diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..d6daca5464 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,3 @@ +# Bump (c) year to 2025 +24d78382a0c195904f054413f208e9e1aa92bc11 +be27b603f804d24a293013298b84307227f263b6 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..70b54cd818 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,43 @@ +Thank you for using RabbitMQ and for taking the time to report an +issue. + +## Does This Belong to GitHub or RabbitMQ Mailing List? + +*Important:* please first read the `CONTRIBUTING.md` document in the +root of this repository. It will help you determine whether your +feedback should be directed to the RabbitMQ mailing list [1] instead. + +## Please Help Maintainers and Contributors Help You + +In order for the RabbitMQ team to investigate your issue, please provide +**as much as possible** of the following details: + +* RabbitMQ version +* Erlang version +* RabbitMQ server and client application log files +* A runnable code sample, terminal transcript or detailed set of + instructions that can be used to reproduce the issue +* RabbitMQ plugin information via `rabbitmq-plugins list` +* Client library version (for all libraries used) +* Operating system, version, and patch level + +Running the `rabbitmq-collect-env` [2] script can provide most of the +information needed. Please make the archive available via a third-party +service and note that **the script does not attempt to scrub any +sensitive data**. + +If your issue involves RabbitMQ management UI or HTTP API, please also provide +the following: + + * Browser and its version + * What management UI page was used (if applicable) + * How the HTTP API requests performed can be reproduced with `curl` + * Operating system on which you are running your browser, and its version + * Errors reported in the JavaScript console (if any) + +This information **greatly speeds up issue investigation** (or makes it +possible to investigate it at all). Please help project maintainers and +contributors to help you by providing it! + +1. https://groups.google.com/forum/#!forum/rabbitmq-users +2. https://github.com/rabbitmq/support-tools/blob/master/scripts/rabbitmq-collect-env diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..4bd618567b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,43 @@ +## Proposed Changes + +Please describe the big picture of your changes here to communicate to the +RabbitMQ team why we should accept this pull request. If it fixes a bug or +resolves a feature request, be sure to link to that issue. + +A pull request that doesn't explain **why** the change was made has a much +lower chance of being accepted. + +If English isn't your first language, don't worry about it and try to +communicate the problem you are trying to solve to the best of your abilities. +As long as we can understand the intent, it's all good. + +## Types of Changes + +What types of changes does your code introduce to this project? +_Put an `x` in the boxes that apply_ + +- [ ] Bugfix (non-breaking change which fixes issue #NNNN) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation (correction or otherwise) +- [ ] Cosmetics (whitespace, appearance) + +## Checklist + +_Put an `x` in the boxes that apply. You can also fill these out after creating +the PR. If you're unsure about any of them, don't hesitate to ask on the +mailing list. We're here to help! This is simply a reminder of what we are +going to look for before merging your code._ + +- [ ] I have read the `CONTRIBUTING.md` document +- [ ] I have signed the CA (see https://cla.pivotal.io/sign/rabbitmq) +- [ ] All tests pass locally with my changes +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] I have added necessary documentation (if appropriate) +- [ ] Any dependent changes have been merged and published in related repositories + +## Further Comments + +If this is a relatively large or complex change, kick off the discussion by +explaining why you chose the solution you did and what alternatives you +considered, etc. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..9832e3fddb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,44 @@ +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 20 + target-branch: "main" + ignore: + - dependency-name: "org.eclipse.jetty:jetty-servlet" + versions: [ "[10.0,)" ] + - dependency-name: "org.slf4j:slf4j-api" + versions: [ "[2.0,)" ] + - dependency-name: "ch.qos.logback:logback-classic" + versions: [ "[1.3,)" ] + - dependency-name: "org.apache.felix:maven-bundle-plugin" + versions: [ "[6.0,)" ] + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 20 + target-branch: "5.x.x-stable" + ignore: + - dependency-name: "org.eclipse.jetty:jetty-servlet" + versions: ["[10.0,)"] + - dependency-name: "org.slf4j:slf4j-api" + versions: [ "[2.0,)" ] + - dependency-name: "ch.qos.logback:logback-classic" + versions: [ "[1.3,)" ] + - dependency-name: "org.apache.felix:maven-bundle-plugin" + versions: [ "[6.0,)" ] + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + target-branch: "main" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + target-branch: "5.x.x-stable" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..570e41e219 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,61 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '21 11 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java', 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + + - run: | + make + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml new file mode 100644 index 0000000000..be18a1befd --- /dev/null +++ b/.github/workflows/publish-snapshot.yml @@ -0,0 +1,33 @@ +name: Publish snapshot + +on: workflow_dispatch + +jobs: + build: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '21' + cache: 'maven' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + - name: Get dependencies + run: make deps + - name: Publish snapshot + run: ./mvnw clean deploy -Psnapshots -DskipITs -DskipTests --no-transfer-progress + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..06569df11c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,50 @@ +name: Release AMQP Java Client + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + - name: Evaluate release type + run: ci/evaluate-release.sh + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '8' + cache: 'maven' + server-id: ${{ env.maven_server_id }} + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + - name: Get dependencies + run: make deps + - name: Release AMQP Java Client (GA) + if: ${{ env.ga_release == 'true' }} + run: | + git config user.name "rabbitmq-ci" + git config user.email "rabbitmq-ci@users.noreply.github.com" + ci/release-java-client.sh + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + - name: Release AMQP Java Client (Milestone/RC) + if: ${{ env.ga_release != 'true' }} + run: | + git config user.name "rabbitmq-ci" + git config user.email "rabbitmq-ci@users.noreply.github.com" + ci/release-java-client.sh + env: + MAVEN_USERNAME: '' + MAVEN_PASSWORD: ${{ secrets.PACKAGECLOUD_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} \ No newline at end of file diff --git a/.github/workflows/test-native-image.yml b/.github/workflows/test-native-image.yml new file mode 100644 index 0000000000..fa73f8f221 --- /dev/null +++ b/.github/workflows/test-native-image.yml @@ -0,0 +1,62 @@ +name: Test GraalVM native image + +on: + schedule: + - cron: '0 4 ? * SUN,THU' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + - name: Checkout tls-gen + uses: actions/checkout@v4 + with: + repository: rabbitmq/tls-gen + path: './tls-gen' + - name: Checkout GraalVM test project + uses: actions/checkout@v4 + with: + repository: rabbitmq/rabbitmq-graal-vm-test + path: './rabbitmq-graal-vm-test' + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Set up GraalVM + uses: graalvm/setup-graalvm@v1 + with: + version: 'latest' + java-version: '21' + cache: 'maven' + - name: Start broker + run: ci/start-broker.sh + - name: Get dependencies + run: make deps + - name: Install client JAR file + run: | + ./mvnw clean install -Psnapshots -DskipITs -DskipTests -Dgpg.skip=true --no-transfer-progress + export ARTEFACT_VERSION=$(cat pom.xml | grep -oPm1 "(?<=)[^<]+") + echo "artefact_version=$ARTEFACT_VERSION" >> $GITHUB_ENV + - name: Package test application + working-directory: rabbitmq-graal-vm-test + run: | + ./mvnw --version + echo "Using RabbitMQ Java Client ${{ env.artefact_version }}" + ./mvnw -q clean package -Damqp-client.version=${{ env.artefact_version }} --no-transfer-progress + - name: Start one-time RPC server + working-directory: rabbitmq-graal-vm-test + run: ./mvnw -q compile exec:java -Damqp-client.version=${{ env.artefact_version }} --no-transfer-progress & + - name: Create native image + working-directory: rabbitmq-graal-vm-test + run: | + native-image -jar target/rabbitmq-graal-vm-test-full.jar \ + --initialize-at-build-time=com.rabbitmq.client \ + --initialize-at-build-time=org.slf4j --no-fallback + - name: Use native image program + working-directory: rabbitmq-graal-vm-test + run: ./rabbitmq-graal-vm-test-full + - name: Stop broker + run: docker stop rabbitmq && docker rm rabbitmq diff --git a/.github/workflows/test-rabbitmq-alphas.yml b/.github/workflows/test-rabbitmq-alphas.yml new file mode 100644 index 0000000000..6fe31dfcb7 --- /dev/null +++ b/.github/workflows/test-rabbitmq-alphas.yml @@ -0,0 +1,63 @@ +name: Test against RabbitMQ alphas + +on: + schedule: + - cron: '0 4 ? * SUN,THU' + pull_request: + branches: + - main + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-24.04 + strategy: + matrix: + rabbitmq-image: + - pivotalrabbitmq/rabbitmq:v4.1.x-otp27 + - pivotalrabbitmq/rabbitmq:main-otp27 + name: Test against ${{ matrix.rabbitmq-image }} + steps: + - uses: actions/checkout@v4 + - name: Checkout tls-gen + uses: actions/checkout@v4 + with: + repository: rabbitmq/tls-gen + path: './tls-gen' + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '21' + cache: 'maven' + - name: Start cluster + run: ci/start-cluster.sh + env: + RABBITMQ_IMAGE: ${{ matrix.rabbitmq-image }} + - name: Get dependencies + run: make deps + - name: Test with NIO + run: | + ./mvnw verify -P use-nio -Drabbitmqctl.bin=DOCKER:rabbitmq0 \ + -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dmaven.javadoc.skip=true \ + --no-transfer-progress + - name: Test with blocking IO + run: | + ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq0 \ + -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dmaven.javadoc.skip=true \ + --no-transfer-progress + - name: Stop cluster + run: docker compose --file ci/cluster/docker-compose.yml down diff --git a/.github/workflows/test-supported-java-versions-5.x.yml b/.github/workflows/test-supported-java-versions-5.x.yml new file mode 100644 index 0000000000..632d383a7f --- /dev/null +++ b/.github/workflows/test-supported-java-versions-5.x.yml @@ -0,0 +1,63 @@ +name: Test against supported Java versions (5.x) + +on: + schedule: + - cron: '0 4 ? * SUN,THU' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-24.04 + strategy: + matrix: + distribution: [ 'temurin' ] + version: [ '8', '11', '17', '21', '24', '25-ea' ] + include: + - distribution: 'semeru' + version: '17' + name: Test against Java ${{ matrix.distribution }} ${{ matrix.version }} + steps: + - uses: actions/checkout@v4 + with: + ref: 5.x.x-stable + - name: Checkout tls-gen + uses: actions/checkout@v4 + with: + repository: rabbitmq/tls-gen + path: './tls-gen' + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.version }} + cache: 'maven' + - name: Start broker + run: ci/start-broker.sh + - name: Get dependencies + run: make deps + - name: Show version + run: ./mvnw --version + - name: Test with NIO + run: | + ./mvnw verify -P use-nio -Drabbitmqctl.bin=DOCKER:rabbitmq \ + -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ + --no-transfer-progress \ + -Dnet.bytebuddy.experimental=true + - name: Test with blocking IO + run: | + ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq \ + -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ + --no-transfer-progress \ + -Dnet.bytebuddy.experimental=true + - name: Stop broker + run: docker stop rabbitmq && docker rm rabbitmq diff --git a/.github/workflows/test-supported-java-versions-main.yml b/.github/workflows/test-supported-java-versions-main.yml new file mode 100644 index 0000000000..8acb19cb30 --- /dev/null +++ b/.github/workflows/test-supported-java-versions-main.yml @@ -0,0 +1,61 @@ +name: Test against supported Java versions (main) + +on: + schedule: + - cron: '0 4 ? * SUN,THU' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-24.04 + strategy: + matrix: + distribution: [ 'temurin' ] + version: [ '8', '11', '17', '21', '24', '25-ea' ] + include: + - distribution: 'semeru' + version: '17' + name: Test against Java ${{ matrix.distribution }} ${{ matrix.version }} + steps: + - uses: actions/checkout@v4 + - name: Checkout tls-gen + uses: actions/checkout@v4 + with: + repository: rabbitmq/tls-gen + path: './tls-gen' + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.version }} + cache: 'maven' + - name: Start broker + run: ci/start-broker.sh + - name: Get dependencies + run: make deps + - name: Show version + run: ./mvnw --version + - name: Test with NIO + run: | + ./mvnw verify -P use-nio -Drabbitmqctl.bin=DOCKER:rabbitmq \ + -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ + --no-transfer-progress \ + -Dnet.bytebuddy.experimental=true + - name: Test with blocking IO + run: | + ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq \ + -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ + --no-transfer-progress \ + -Dnet.bytebuddy.experimental=true + - name: Stop broker + run: docker stop rabbitmq && docker rm rabbitmq diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..126c9ffe5f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,67 @@ +name: Test against RabbitMQ stable + +on: + pull_request: + branches: + - main + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + - name: Checkout tls-gen + uses: actions/checkout@v4 + with: + repository: rabbitmq/tls-gen + path: './tls-gen' + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '21' + cache: 'maven' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + - name: Start cluster + run: ci/start-cluster.sh + - name: Get dependencies + run: make deps + - name: Test with NIO + run: | + ./mvnw verify -P use-nio -Drabbitmqctl.bin=DOCKER:rabbitmq0 \ + -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dmaven.javadoc.skip=true \ + --no-transfer-progress + - name: Test with blocking IO + run: | + ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq0 \ + -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ + -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ + -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dmaven.javadoc.skip=true \ + -Dtest-client-cert.password= -Dtest-tls-certs.dir=rabbitmq-configuration/tls \ + --no-transfer-progress + - name: Stop cluster + run: docker compose --file ci/cluster/docker-compose.yml down + - name: Publish snapshot + if: ${{ github.event_name != 'pull_request' }} + run: ./mvnw clean deploy -Psnapshots -DskipITs -DskipTests --no-transfer-progress + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} diff --git a/.gitignore b/.gitignore index b552f873b8..f52592ac2c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,11 +8,11 @@ *.iml *.ipr *.iws +.DS_Store \#~ /.idea/ -/build/ -/cover/ -/dist/ -/ebin/ -/out/ -/tmp/ +/deps/ +/target/ +/.classpath +/.project +/.settings diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 0000000000..f373d1de10 --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1 @@ +-Dmaven.wagon.http.retryHandler.count=10 diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000..cb28b0e37c Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..1a60da7935 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..08697906fd --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,44 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of fostering an open +and welcoming community, we pledge to respect all people who contribute through reporting +issues, posting feature requests, updating documentation, submitting pull requests or +patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for +everyone, regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, body size, race, ethnicity, age, +religion, or nationality. + +Examples of unacceptable behavior by participants include: + + * The use of sexualized language or imagery + * Personal attacks + * Trolling or insulting/derogatory comments + * Public or private harassment + * Publishing other's private information, such as physical or electronic addresses, + without explicit permission + * Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this +Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors +that they deem inappropriate, threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to fairly and +consistently applying these principles to every aspect of managing this project. Project +maintainers who do not follow or enforce the Code of Conduct may be permanently removed +from the project team. + +This Code of Conduct applies both within project spaces and in public spaces when an +individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by +contacting a project maintainer at [info@rabbitmq.com](mailto:info@rabbitmq.com). All complaints will +be reviewed and investigated and will result in a response that is deemed necessary and +appropriate to the circumstances. Maintainers are obligated to maintain confidentiality +with regard to the reporter of an incident. + +This Code of Conduct is adapted from the +[Contributor Covenant](https://contributor-covenant.org), version 1.3.0, available at +[contributor-covenant.org/version/1/3/0/](https://contributor-covenant.org/version/1/3/0/) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69a4b4a437..592e7ced57 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,51 +1,99 @@ +Thank you for using RabbitMQ and for taking the time to contribute to the project. +This document has two main parts: + + * when and how to file GitHub issues for RabbitMQ projects + * how to submit pull requests + +They intend to save you and RabbitMQ maintainers some time, so please +take a moment to read through them. + ## Overview -RabbitMQ projects use pull requests to discuss, collaborate on and accept code contributions. -Pull requests is the primary place of discussing code changes. +### GitHub issues -## How to Contribute +The RabbitMQ team uses GitHub issues for _specific actionable items_ that +engineers can work on. This assumes the following: -The process is fairly standard: +* GitHub issues are not used for questions, investigations, root cause + analysis, discussions of potential issues, etc (as defined by this team) +* Enough information is provided by the reporter for maintainers to work with - * Fork the repository or repositories you plan on contributing to - * Clone [RabbitMQ umbrella repository](https://github.com/rabbitmq/rabbitmq-public-umbrella) - * `cd umbrella`, `make co` - * Create a branch with a descriptive name in the relevant repositories - * Make your changes, run tests, commit with a [descriptive message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), push to your fork - * Submit pull requests with an explanation what has been changed and **why** - * Submit a filled out and signed [Contributor Agreement](https://github.com/rabbitmq/ca#how-to-submit) if needed (see below) - * Be patient. We will get to your pull request eventually +The team receives many questions through various venues every single +day. Frequently, these questions do not include the necessary details +the team needs to begin useful work. GitHub issues can very quickly +turn into a something impossible to navigate and make sense +of. Because of this, questions, investigations, root cause analysis, +and discussions of potential features are all considered to be +[mailing list][rmq-users] material. If you are unsure where to begin, +the [RabbitMQ users mailing list][rmq-users] is the right place. + +Getting all the details necessary to reproduce an issue, make a +conclusion or even form a hypothesis about what's happening can take a +fair amount of time. Please help others help you by providing a way to +reproduce the behavior you're observing, or at least sharing as much +relevant information as possible on the [RabbitMQ users mailing +list][rmq-users]. + +Please provide versions of the software used: -If what you are going to work on is a substantial change, please first ask the core team -of their opinion on [RabbitMQ mailing list](https://groups.google.com/forum/#!forum/rabbitmq-users). + * RabbitMQ server + * Erlang + * Operating system version (and distribution, if applicable) + * All client libraries used + * RabbitMQ plugins (if applicable) +The following information greatly helps in investigating and reproducing issues: -## (Brief) Code of Conduct + * RabbitMQ server logs + * A code example or terminal transcript that can be used to reproduce + * Full exception stack traces (a single line message is not enough!) + * `rabbitmqctl report` and `rabbitmqctl environment` output + * Other relevant details about the environment and workload, e.g. a traffic capture + * Feel free to edit out hostnames and other potentially sensitive information. -In one line: don't be a dick. +To make collecting much of this and other environment information, use +the [`rabbitmq-collect-env`][rmq-collect-env] script. It will produce an archive with +server logs, operating system logs, output of certain diagnostics commands and so on. +Please note that **no effort is made to scrub any information that may be sensitive**. -Be respectful to the maintainers and other contributors. Open source -contributors put long hours into developing projects and doing user -support. Those projects and user support are available for free. We -believe this deserves some respect. +### Pull Requests -Be respectful to people of all races, genders, religious beliefs and -political views. Regardless of how brilliant a pull request is -technically, we will not tolerate disrespectful or aggressive -behaviour. +RabbitMQ projects use pull requests to discuss, collaborate on and accept code contributions. +Pull requests is the primary place of discussing code changes. + +Here's the recommended workflow: + + * [Fork the repository][github-fork] or repositories you plan on contributing to. If multiple + repositories are involved in addressing the same issue, please use the same branch name + in each repository + * Create a branch with a descriptive name in the relevant repositories + * Make your changes, run tests (usually with `make tests`), commit with a + [descriptive message][git-commit-msgs], push to your fork + * Submit pull requests with an explanation what has been changed and **why** + * Submit a filled out and signed [Contributor Agreement][ca-agreement] if needed (see below) + * Be patient. We will get to your pull request eventually -Contributors who violate this straightforward Code of Conduct will see -their pull requests closed and locked. +If what you are going to work on is a substantial change, please first +ask the core team for their opinion on the [RabbitMQ users mailing list][rmq-users]. +## Code of Conduct -## Contributor Agreement +See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). -If you want to contribute a non-trivial change, please submit a signed copy of our -[Contributor Agreement](https://github.com/rabbitmq/ca#how-to-submit) around the time -you submit your pull request. This will make it much easier (in some cases, possible) -for the RabbitMQ team at Pivotal to merge your contribution. +## Contributor Agreement +If you want to contribute a non-trivial change, please submit a signed +copy of our [Contributor Agreement][ca-agreement] around the time you +submit your pull request. This will make it much easier (in some +cases, possible) for the RabbitMQ team at Pivotal to merge your +contribution. ## Where to Ask Questions -If something isn't clear, feel free to ask on our [mailing list](https://groups.google.com/forum/#!forum/rabbitmq-users). +If something isn't clear, feel free to ask on our [mailing list][rmq-users]. + +[rmq-collect-env]: https://github.com/rabbitmq/support-tools/blob/master/scripts/rabbitmq-collect-env +[git-commit-msgs]: https://chris.beams.io/posts/git-commit/ +[rmq-users]: https://groups.google.com/forum/#!forum/rabbitmq-users +[ca-agreement]: https://cla.pivotal.io/sign/rabbitmq +[github-fork]: https://help.github.com/articles/fork-a-repo/ diff --git a/LICENSE b/LICENSE index dc3c875662..9e58613784 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,12 @@ This package, the RabbitMQ Java client library, is triple-licensed under -the Mozilla Public License 1.1 ("MPL"), the GNU General Public License +the Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, please see LICENSE-APACHE2. +This means that you may choose one of these licenses when including or +using this software in your own. + The RabbitMQ Java client library includes third-party software under the ASL. For this license, please see LICENSE-APACHE2. For attribution of copyright and other details of provenance, please refer to the source code. diff --git a/LICENSE-APACHE2 b/LICENSE-APACHE2 index d645695673..62589edd12 100644 --- a/LICENSE-APACHE2 +++ b/LICENSE-APACHE2 @@ -1,7 +1,7 @@ Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -193,7 +193,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/LICENSE-MPL-RabbitMQ b/LICENSE-MPL-RabbitMQ index b0325a0e49..6f455abd56 100644 --- a/LICENSE-MPL-RabbitMQ +++ b/LICENSE-MPL-RabbitMQ @@ -1,467 +1,368 @@ - MOZILLA PUBLIC LICENSE - Version 1.1 - - --------------- - -1. Definitions. - - 1.0.1. "Commercial Use" means distribution or otherwise making the - Covered Code available to a third party. - - 1.1. "Contributor" means each entity that creates or contributes to - the creation of Modifications. - - 1.2. "Contributor Version" means the combination of the Original - Code, prior Modifications used by a Contributor, and the Modifications - made by that particular Contributor. - - 1.3. "Covered Code" means the Original Code or Modifications or the - combination of the Original Code and Modifications, in each case - including portions thereof. - - 1.4. "Electronic Distribution Mechanism" means a mechanism generally - accepted in the software development community for the electronic - transfer of data. - - 1.5. "Executable" means Covered Code in any form other than Source - Code. - - 1.6. "Initial Developer" means the individual or entity identified - as the Initial Developer in the Source Code notice required by Exhibit - A. - - 1.7. "Larger Work" means a work which combines Covered Code or - portions thereof with code not governed by the terms of this License. - - 1.8. "License" means this document. - - 1.8.1. "Licensable" means having the right to grant, to the maximum - extent possible, whether at the time of the initial grant or - subsequently acquired, any and all of the rights conveyed herein. - - 1.9. "Modifications" means any addition to or deletion from the - substance or structure of either the Original Code or any previous - Modifications. When Covered Code is released as a series of files, a - Modification is: - A. Any addition to or deletion from the contents of a file - containing Original Code or previous Modifications. - - B. Any new file that contains any part of the Original Code or - previous Modifications. - - 1.10. "Original Code" means Source Code of computer software code - which is described in the Source Code notice required by Exhibit A as - Original Code, and which, at the time of its release under this - License is not already Covered Code governed by this License. - - 1.10.1. "Patent Claims" means any patent claim(s), now owned or - hereafter acquired, including without limitation, method, process, - and apparatus claims, in any patent Licensable by grantor. - - 1.11. "Source Code" means the preferred form of the Covered Code for - making modifications to it, including all modules it contains, plus - any associated interface definition files, scripts used to control - compilation and installation of an Executable, or source code - differential comparisons against either the Original Code or another - well known, available Covered Code of the Contributor's choice. The - Source Code can be in a compressed or archival form, provided the - appropriate decompression or de-archiving software is widely available - for no charge. - - 1.12. "You" (or "Your") means an individual or a legal entity - exercising rights under, and complying with all of the terms of, this - License or a future version of this License issued under Section 6.1. - For legal entities, "You" includes any entity which controls, is - controlled by, or is under common control with You. For purposes of - this definition, "control" means (a) the power, direct or indirect, - to cause the direction or management of such entity, whether by - contract or otherwise, or (b) ownership of more than fifty percent - (50%) of the outstanding shares or beneficial ownership of such - entity. - -2. Source Code License. - - 2.1. The Initial Developer Grant. - The Initial Developer hereby grants You a world-wide, royalty-free, - non-exclusive license, subject to third party intellectual property - claims: - (a) under intellectual property rights (other than patent or - trademark) Licensable by Initial Developer to use, reproduce, - modify, display, perform, sublicense and distribute the Original - Code (or portions thereof) with or without Modifications, and/or - as part of a Larger Work; and - - (b) under Patents Claims infringed by the making, using or - selling of Original Code, to make, have made, use, practice, - sell, and offer for sale, and/or otherwise dispose of the - Original Code (or portions thereof). - - (c) the licenses granted in this Section 2.1(a) and (b) are - effective on the date Initial Developer first distributes - Original Code under the terms of this License. - - (d) Notwithstanding Section 2.1(b) above, no patent license is - granted: 1) for code that You delete from the Original Code; 2) - separate from the Original Code; or 3) for infringements caused - by: i) the modification of the Original Code or ii) the - combination of the Original Code with other software or devices. - - 2.2. Contributor Grant. - Subject to third party intellectual property claims, each Contributor - hereby grants You a world-wide, royalty-free, non-exclusive license - - (a) under intellectual property rights (other than patent or - trademark) Licensable by Contributor, to use, reproduce, modify, - display, perform, sublicense and distribute the Modifications - created by such Contributor (or portions thereof) either on an - unmodified basis, with other Modifications, as Covered Code - and/or as part of a Larger Work; and - - (b) under Patent Claims infringed by the making, using, or - selling of Modifications made by that Contributor either alone - and/or in combination with its Contributor Version (or portions - of such combination), to make, use, sell, offer for sale, have - made, and/or otherwise dispose of: 1) Modifications made by that - Contributor (or portions thereof); and 2) the combination of - Modifications made by that Contributor with its Contributor - Version (or portions of such combination). - - (c) the licenses granted in Sections 2.2(a) and 2.2(b) are - effective on the date Contributor first makes Commercial Use of - the Covered Code. - - (d) Notwithstanding Section 2.2(b) above, no patent license is - granted: 1) for any code that Contributor has deleted from the - Contributor Version; 2) separate from the Contributor Version; - 3) for infringements caused by: i) third party modifications of - Contributor Version or ii) the combination of Modifications made - by that Contributor with other software (except as part of the - Contributor Version) or other devices; or 4) under Patent Claims - infringed by Covered Code in the absence of Modifications made by - that Contributor. - -3. Distribution Obligations. - - 3.1. Application of License. - The Modifications which You create or to which You contribute are - governed by the terms of this License, including without limitation - Section 2.2. The Source Code version of Covered Code may be - distributed only under the terms of this License or a future version - of this License released under Section 6.1, and You must include a - copy of this License with every copy of the Source Code You - distribute. You may not offer or impose any terms on any Source Code - version that alters or restricts the applicable version of this - License or the recipients' rights hereunder. However, You may include - an additional document offering the additional rights described in - Section 3.5. - - 3.2. Availability of Source Code. - Any Modification which You create or to which You contribute must be - made available in Source Code form under the terms of this License - either on the same media as an Executable version or via an accepted - Electronic Distribution Mechanism to anyone to whom you made an - Executable version available; and if made available via Electronic - Distribution Mechanism, must remain available for at least twelve (12) - months after the date it initially became available, or at least six - (6) months after a subsequent version of that particular Modification - has been made available to such recipients. You are responsible for - ensuring that the Source Code version remains available even if the - Electronic Distribution Mechanism is maintained by a third party. - - 3.3. Description of Modifications. - You must cause all Covered Code to which You contribute to contain a - file documenting the changes You made to create that Covered Code and - the date of any change. You must include a prominent statement that - the Modification is derived, directly or indirectly, from Original - Code provided by the Initial Developer and including the name of the - Initial Developer in (a) the Source Code, and (b) in any notice in an - Executable version or related documentation in which You describe the - origin or ownership of the Covered Code. - - 3.4. Intellectual Property Matters - (a) Third Party Claims. - If Contributor has knowledge that a license under a third party's - intellectual property rights is required to exercise the rights - granted by such Contributor under Sections 2.1 or 2.2, - Contributor must include a text file with the Source Code - distribution titled "LEGAL" which describes the claim and the - party making the claim in sufficient detail that a recipient will - know whom to contact. If Contributor obtains such knowledge after - the Modification is made available as described in Section 3.2, - Contributor shall promptly modify the LEGAL file in all copies - Contributor makes available thereafter and shall take other steps - (such as notifying appropriate mailing lists or newsgroups) - reasonably calculated to inform those who received the Covered - Code that new knowledge has been obtained. - - (b) Contributor APIs. - If Contributor's Modifications include an application programming - interface and Contributor has knowledge of patent licenses which - are reasonably necessary to implement that API, Contributor must - also include this information in the LEGAL file. - - (c) Representations. - Contributor represents that, except as disclosed pursuant to - Section 3.4(a) above, Contributor believes that Contributor's - Modifications are Contributor's original creation(s) and/or - Contributor has sufficient rights to grant the rights conveyed by - this License. - - 3.5. Required Notices. - You must duplicate the notice in Exhibit A in each file of the Source - Code. If it is not possible to put such notice in a particular Source - Code file due to its structure, then You must include such notice in a - location (such as a relevant directory) where a user would be likely - to look for such a notice. If You created one or more Modification(s) - You may add your name as a Contributor to the notice described in - Exhibit A. You must also duplicate this License in any documentation - for the Source Code where You describe recipients' rights or ownership - rights relating to Covered Code. You may choose to offer, and to - charge a fee for, warranty, support, indemnity or liability - obligations to one or more recipients of Covered Code. However, You - may do so only on Your own behalf, and not on behalf of the Initial - Developer or any Contributor. You must make it absolutely clear than - any such warranty, support, indemnity or liability obligation is - offered by You alone, and You hereby agree to indemnify the Initial - Developer and every Contributor for any liability incurred by the - Initial Developer or such Contributor as a result of warranty, - support, indemnity or liability terms You offer. - - 3.6. Distribution of Executable Versions. - You may distribute Covered Code in Executable form only if the - requirements of Section 3.1-3.5 have been met for that Covered Code, - and if You include a notice stating that the Source Code version of - the Covered Code is available under the terms of this License, - including a description of how and where You have fulfilled the - obligations of Section 3.2. The notice must be conspicuously included - in any notice in an Executable version, related documentation or - collateral in which You describe recipients' rights relating to the - Covered Code. You may distribute the Executable version of Covered - Code or ownership rights under a license of Your choice, which may - contain terms different from this License, provided that You are in - compliance with the terms of this License and that the license for the - Executable version does not attempt to limit or alter the recipient's - rights in the Source Code version from the rights set forth in this - License. If You distribute the Executable version under a different - license You must make it absolutely clear that any terms which differ - from this License are offered by You alone, not by the Initial - Developer or any Contributor. You hereby agree to indemnify the - Initial Developer and every Contributor for any liability incurred by - the Initial Developer or such Contributor as a result of any such - terms You offer. - - 3.7. Larger Works. - You may create a Larger Work by combining Covered Code with other code - not governed by the terms of this License and distribute the Larger - Work as a single product. In such a case, You must make sure the - requirements of this License are fulfilled for the Covered Code. - -4. Inability to Comply Due to Statute or Regulation. - - If it is impossible for You to comply with any of the terms of this - License with respect to some or all of the Covered Code due to - statute, judicial order, or regulation then You must: (a) comply with - the terms of this License to the maximum extent possible; and (b) - describe the limitations and the code they affect. Such description - must be included in the LEGAL file described in Section 3.4 and must - be included with all distributions of the Source Code. Except to the - extent prohibited by statute or regulation, such description must be - sufficiently detailed for a recipient of ordinary skill to be able to - understand it. - -5. Application of this License. - - This License applies to code to which the Initial Developer has - attached the notice in Exhibit A and to related Covered Code. - -6. Versions of the License. - - 6.1. New Versions. - Netscape Communications Corporation ("Netscape") may publish revised - and/or new versions of the License from time to time. Each version - will be given a distinguishing version number. - - 6.2. Effect of New Versions. - Once Covered Code has been published under a particular version of the - License, You may always continue to use it under the terms of that - version. You may also choose to use such Covered Code under the terms - of any subsequent version of the License published by Netscape. No one - other than Netscape has the right to modify the terms applicable to - Covered Code created under this License. - - 6.3. Derivative Works. - If You create or use a modified version of this License (which you may - only do in order to apply it to code which is not already Covered Code - governed by this License), You must (a) rename Your license so that - the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", - "MPL", "NPL" or any confusingly similar phrase do not appear in your - license (except to note that your license differs from this License) - and (b) otherwise make it clear that Your version of the license - contains terms which differ from the Mozilla Public License and - Netscape Public License. (Filling in the name of the Initial - Developer, Original Code or Contributor in the notice described in - Exhibit A shall not of themselves be deemed to be modifications of - this License.) - -7. DISCLAIMER OF WARRANTY. - - COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, - WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, - WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF - DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. - THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE - IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, - YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE - COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER - OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF - ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. - -8. TERMINATION. - - 8.1. This License and the rights granted hereunder will terminate - automatically if You fail to comply with terms herein and fail to cure - such breach within 30 days of becoming aware of the breach. All - sublicenses to the Covered Code which are properly granted shall - survive any termination of this License. Provisions which, by their - nature, must remain in effect beyond the termination of this License - shall survive. - - 8.2. If You initiate litigation by asserting a patent infringement - claim (excluding declatory judgment actions) against Initial Developer - or a Contributor (the Initial Developer or Contributor against whom - You file such action is referred to as "Participant") alleging that: - - (a) such Participant's Contributor Version directly or indirectly - infringes any patent, then any and all rights granted by such - Participant to You under Sections 2.1 and/or 2.2 of this License - shall, upon 60 days notice from Participant terminate prospectively, - unless if within 60 days after receipt of notice You either: (i) - agree in writing to pay Participant a mutually agreeable reasonable - royalty for Your past and future use of Modifications made by such - Participant, or (ii) withdraw Your litigation claim with respect to - the Contributor Version against such Participant. If within 60 days - of notice, a reasonable royalty and payment arrangement are not - mutually agreed upon in writing by the parties or the litigation claim - is not withdrawn, the rights granted by Participant to You under - Sections 2.1 and/or 2.2 automatically terminate at the expiration of - the 60 day notice period specified above. - - (b) any software, hardware, or device, other than such Participant's - Contributor Version, directly or indirectly infringes any patent, then - any rights granted to You by such Participant under Sections 2.1(b) - and 2.2(b) are revoked effective as of the date You first made, used, - sold, distributed, or had made, Modifications made by that - Participant. - - 8.3. If You assert a patent infringement claim against Participant - alleging that such Participant's Contributor Version directly or - indirectly infringes any patent where such claim is resolved (such as - by license or settlement) prior to the initiation of patent - infringement litigation, then the reasonable value of the licenses - granted by such Participant under Sections 2.1 or 2.2 shall be taken - into account in determining the amount or value of any payment or - license. - - 8.4. In the event of termination under Sections 8.1 or 8.2 above, - all end user license agreements (excluding distributors and resellers) - which have been validly granted by You or any distributor hereunder - prior to termination shall survive termination. - -9. LIMITATION OF LIABILITY. - - UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT - (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL - DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, - OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR - ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY - CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, - WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER - COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN - INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF - LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY - RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW - PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE - EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO - THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. - -10. U.S. GOVERNMENT END USERS. - - The Covered Code is a "commercial item," as that term is defined in - 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer - software" and "commercial computer software documentation," as such - terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 - C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), - all U.S. Government End Users acquire Covered Code with only those - rights set forth herein. - -11. MISCELLANEOUS. - - This License represents the complete agreement concerning subject - matter hereof. If any provision of this License is held to be - unenforceable, such provision shall be reformed only to the extent - necessary to make it enforceable. This License shall be governed by - California law provisions (except to the extent applicable law, if - any, provides otherwise), excluding its conflict-of-law provisions. - With respect to disputes in which at least one party is a citizen of, - or an entity chartered or registered to do business in the United - States of America, any litigation relating to this License shall be - subject to the jurisdiction of the Federal Courts of the Northern - District of California, with venue lying in Santa Clara County, - California, with the losing party responsible for costs, including - without limitation, court costs and reasonable attorneys' fees and - expenses. The application of the United Nations Convention on - Contracts for the International Sale of Goods is expressly excluded. - Any law or regulation which provides that the language of a contract - shall be construed against the drafter shall not apply to this - License. - -12. RESPONSIBILITY FOR CLAIMS. - - As between Initial Developer and the Contributors, each party is - responsible for claims and damages arising, directly or indirectly, - out of its utilization of rights under this License and You agree to - work with Initial Developer and Contributors to distribute such - responsibility on an equitable basis. Nothing herein is intended or - shall be deemed to constitute any admission of liability. - -13. MULTIPLE-LICENSED CODE. - - Initial Developer may designate portions of the Covered Code as - "Multiple-Licensed". "Multiple-Licensed" means that the Initial - Developer permits you to utilize portions of the Covered Code under - Your choice of the MPL or the alternative licenses, if any, specified - by the Initial Developer in the file described in Exhibit A. - -EXHIBIT A -Mozilla Public License. - - ``The contents of this file are subject to the Mozilla Public License - Version 1.1 (the "License"); you may not use this file except in - compliance with the License. You may obtain a copy of the License at - http://www.mozilla.org/MPL/ - - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the - License for the specific language governing rights and limitations - under the License. - - The Original Code is RabbitMQ. - - The Initial Developer of the Original Code is GoPivotal, Inc. - Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. - - Alternatively, the contents of this file may be used under the terms - of the GNU General Public License version 2 (the "GPL2"), or - the Apache License version 2 (the "ASL2") in which case the - provisions of GPL2 or the ASL2 are applicable instead of those - above. If you wish to allow use of your version of this file only - under the terms of the GPL2 or the ASL2 and not to allow others to use - your version of this file under the MPL, indicate your decision by - deleting the provisions above and replace them with the notice and - other provisions required by the GPL2 or the ASL2. If you do not delete - the provisions above, a recipient may use your version of this file - under either the MPL, the GPL2 or the ASL2.'' - - [NOTE: The text of this Exhibit A may differ slightly from the text of - the notices in the Source Code files of the Original Code. You should - use the text of this Exhibit A rather than the text found in the - Original Code Source Code for Your Modifications.] +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +Copyright (c) 2007-2023 Broadcom. All Rights Reserved. +The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. diff --git a/Makefile b/Makefile index bf5df82554..f320fd349f 100644 --- a/Makefile +++ b/Makefile @@ -1,94 +1,51 @@ -VERSION=0.0.0 -PACKAGE_NAME=rabbitmq-java-client -JAVADOC_ARCHIVE=$(PACKAGE_NAME)-javadoc-$(VERSION) -SRC_ARCHIVE=$(PACKAGE_NAME)-$(VERSION) -SIGNING_KEY=056E8E56 -GNUPG_PATH=~ +MVN ?= mvn +MVN_FLAGS ?= -WEB_URL=http://www.rabbitmq.com/ -NEXUS_STAGE_URL=http://oss.sonatype.org/service/local/staging/deploy/maven2 -MAVEN_NEXUS_VERSION=1.7 +ifndef DEPS_DIR +ifneq ($(wildcard ../../UMBRELLA.md),) +DEPS_DIR = .. +else +DEPS_DIR = deps +endif +endif -AMQP_CODEGEN_DIR=$(shell fgrep sibling.codegen.dir build.properties | sed -e 's:sibling\.codegen\.dir=::') +MVN_FLAGS += -Ddeps.dir="$(abspath $(DEPS_DIR))" -MAVEN_RSYNC_DESTINATION=maven@195.224.125.254:/home/maven/rabbitmq-java-client/ +.PHONY: all deps tests clean distclean -all: - ant build +all: deps + $(MVN) $(MVN_FLAGS) compile -clean: - ant clean - -distclean: clean - make -C $(AMQP_CODEGEN_DIR) clean - -dist: distclean srcdist dist_all maven-bundle - -dist_all: dist1.5 javadoc-archive +deps: $(DEPS_DIR)/rabbitmq_codegen + @: -jar: - ant jar +dist: clean + $(MVN) $(MVN_FLAGS) -DskipTests=true -Dmaven.javadoc.failOnError=false package javadoc:javadoc -maven-bundle: - ant -Dimpl.version=$(VERSION) maven-bundle +$(DEPS_DIR)/rabbitmq_codegen: + git clone -n --depth=1 --filter=tree:0 https://github.com/rabbitmq/rabbitmq-server.git $(DEPS_DIR)/rabbitmq-server + git -C $(DEPS_DIR)/rabbitmq-server sparse-checkout set --no-cone deps/rabbitmq_codegen + git -C $(DEPS_DIR)/rabbitmq-server checkout + cp -R $(DEPS_DIR)/rabbitmq-server/deps/rabbitmq_codegen "$@" + rm -rf $(DEPS_DIR)/rabbitmq-server -dist1.5: - ant -Ddist.out=build/$(PACKAGE_NAME)-bin-$(VERSION) -Dimpl.version=$(VERSION) dist - $(MAKE) post-dist TARBALL_NAME=$(PACKAGE_NAME)-bin-$(VERSION) +tests: deps + $(MVN) $(MVN_FLAGS) verify -javadoc-archive: - ant javadoc - cp -Rp build/doc/api build/$(JAVADOC_ARCHIVE) - (cd build; tar -zcf $(JAVADOC_ARCHIVE).tar.gz $(JAVADOC_ARCHIVE)) - (cd build; zip -q -r $(JAVADOC_ARCHIVE).zip $(JAVADOC_ARCHIVE)) - (cd build; rm -rf $(JAVADOC_ARCHIVE)) +deploy: + $(MVN) $(MVN_FLAGS) deploy -post-dist: - @[ -n "$(TARBALL_NAME)" ] || (echo "Please set TARBALL_NAME."; false) - chmod a+x build/$(TARBALL_NAME)/*.sh - cp LICENSE* build/$(TARBALL_NAME) - (cd build; tar -zcf $(TARBALL_NAME).tar.gz $(TARBALL_NAME)) - (cd build; zip -q -r $(TARBALL_NAME).zip $(TARBALL_NAME)) - (cd build; rm -rf $(TARBALL_NAME)) - -srcdist: distclean - mkdir -p build/$(SRC_ARCHIVE) - cp -Rp `ls | grep -v '^\(build\|README.in\)$$'` build/$(SRC_ARCHIVE) - - mkdir -p build/$(SRC_ARCHIVE)/codegen - cp -r $(AMQP_CODEGEN_DIR)/* build/$(SRC_ARCHIVE)/codegen/. +clean: + $(MVN) $(MVN_FLAGS) clean - if [ -f README.in ]; then \ - cp README.in build/$(SRC_ARCHIVE)/README; \ - elinks -dump -no-references -no-numbering $(WEB_URL)build-java-client.html \ - >> build/$(SRC_ARCHIVE)/README; \ - fi - (cd build; tar -zcf $(SRC_ARCHIVE).tar.gz $(SRC_ARCHIVE)) - (cd build; zip -q -r $(SRC_ARCHIVE).zip $(SRC_ARCHIVE)) - (cd build; rm -rf $(SRC_ARCHIVE)) +distclean: clean + $(MAKE) -C $(DEPS_DIR)/rabbitmq_codegen clean -stage-and-promote-maven-bundle: - ( \ - cd build/bundle; \ - NEXUS_USERNAME=`cat $(GNUPG_PATH)/../nexus/username`; \ - NEXUS_PASSWORD=`cat $(GNUPG_PATH)/../nexus/password`; \ - VERSION=$(VERSION) \ - SIGNING_KEY=$(SIGNING_KEY) \ - GNUPG_PATH=$(GNUPG_PATH) \ - CREDS="$$NEXUS_USERNAME:$$NEXUS_PASSWORD" \ - ../../nexus-upload.sh \ - amqp-client-$(VERSION).pom \ - amqp-client-$(VERSION).jar \ - amqp-client-$(VERSION)-javadoc.jar \ - amqp-client-$(VERSION)-sources.jar && \ - mvn org.sonatype.plugins:nexus-maven-plugin:$(MAVEN_NEXUS_VERSION):staging-close \ - org.sonatype.plugins:nexus-maven-plugin:$(MAVEN_NEXUS_VERSION):staging-promote \ - -Dnexus.url=http://oss.sonatype.org \ - -Dnexus.username=$$NEXUS_USERNAME \ - -Dnexus.password=$$NEXUS_PASSWORD \ - -Dnexus.promote.autoSelectOverride=true \ - -DtargetRepositoryId=releases \ - -B \ - -Dnexus.description="Public release of $$VERSION" \ - ) +.PHONY: cluster-other-node +cluster-other-node: + $(exec_verbose) $(RABBITMQCTL) -n $(OTHER_NODE) stop_app + $(verbose) $(RABBITMQCTL) -n $(OTHER_NODE) reset + $(verbose) $(RABBITMQCTL) -n $(OTHER_NODE) join_cluster \ + $(if $(MAIN_NODE),$(MAIN_NODE),$(RABBITMQ_NODENAME)@$$(hostname -s)) + $(verbose) $(RABBITMQCTL) -n $(OTHER_NODE) start_app diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000000..2d7dd4f58d --- /dev/null +++ b/README.adoc @@ -0,0 +1,263 @@ +:client-stable: 5.24.0 +:client-rc: 5.17.0.RC2 +:client-snapshot: 5.25.0-SNAPSHOT + += RabbitMQ Java Client + +image:https://maven-badges.herokuapp.com/maven-central/com.rabbitmq/amqp-client/badge.svg["Maven Central", link="https://maven-badges.herokuapp.com/maven-central/com.rabbitmq/amqp-client"] +image:https://github.com/rabbitmq/rabbitmq-java-client/actions/workflows/test.yml/badge.svg["Build Status", link="https://github.com/rabbitmq/rabbitmq-java-client/actions/workflows/test.yml"] + +This repository contains source code of the https://www.rabbitmq.com/client-libraries/java-api-guide[RabbitMQ Java client]. +The client is maintained by the https://github.com/rabbitmq/[RabbitMQ team at Broadcom]. + +== RabbitMQ Server Compatibility + +This client releases are independent of RabbitMQ server releases and can be used with RabbitMQ server `4.x` and `3.x` (note that the `3.x` series is https://www.rabbitmq.com/release-information[out of community support]). + +== Minimum Supported JDK Version + +They require Java 8 or higher. + +== Dependency (Maven Artifact) + +=== Stable + +==== Maven + +.pom.xml +[source,xml,subs="attributes,specialcharacters"] +---- + + com.rabbitmq + amqp-client + {client-stable} + +---- + +==== Gradle + +.build.gradle +[source,groovy,subs="attributes,specialcharacters"] +---- +compile 'com.rabbitmq:amqp-client:{client-stable}' +---- + +//// +=== Milestones and Release Candidates + +==== Maven + +.pom.xml +[source,xml,subs="attributes,specialcharacters"] +---- + + com.rabbitmq + amqp-client + {client-rc} + +---- + +Milestones and release candidates are available on the RabbitMQ Milestone Repository: + +.pom.xml +[source,xml,subs="attributes,specialcharacters"] +---- + + + packagecloud-rabbitmq-maven-milestones + https://packagecloud.io/rabbitmq/maven-milestones/maven2 + + true + + + false + + + +---- + +==== Gradle + +.build.gradle +[source,groovy,subs="attributes,specialcharacters"] +---- +compile 'com.rabbitmq:amqp-client:{client-rc}' +---- + +Milestones and release candidates are available on the RabbitMQ Milestone Repository: + +.build.gradle +[source,groovy,subs="attributes,specialcharacters"] +---- +repositories { + maven { + url "https://packagecloud.io/rabbitmq/maven-milestones/maven2" + } +} +---- +//// + +=== Snapshots + +==== Maven + +.pom.xml +[source,xml,subs="attributes,specialcharacters"] +---- + + com.rabbitmq + amqp-client + {client-snapshot} + +---- + +Snapshots are available on the Sonatype OSS snapshot repository: + +.pom.xml +[source,xml,subs="attributes,specialcharacters"] +---- + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + true + + + false + + + +---- + +==== Gradle + +.build.gradle +[source,groovy,subs="attributes,specialcharacters"] +---- +compile 'com.rabbitmq:amqp-client:{client-snapshot}' +---- + +Snapshots are available on the Sonatype OSS snapshot repository: + +.build.gradle +[source,groovy,subs="attributes,specialcharacters"] +---- +repositories { + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + mavenCentral() +} +---- + +=== 4.x Series + +**As of 1 January 2021 the 4.x branch is no longer supported**. + +== Experimenting with JShell + +You can experiment with the client from JShell. This requires Java 9 or more. + +[source,shell] +---- +git clone https://github.com/rabbitmq/rabbitmq-java-client.git +cd rabbitmq-java-client +./mvnw test-compile jshell:run +... +import com.rabbitmq.client.* +ConnectionFactory cf = new ConnectionFactory() +Connection c = cf.newConnection() +... +c.close() +/exit +---- + +== Building from Source + +=== Getting the Project and its Dependencies + +[source,shell] +---- +git clone git@github.com:rabbitmq/rabbitmq-java-client.git +cd rabbitmq-java-client +make deps +---- + +=== Building the JAR File + +[source,shell] +---- +./mvnw clean package -Dmaven.test.skip +---- + +=== Launching Tests with the Broker Running in a Docker Container + +Run the broker: + +[source,shell] +---- +docker run -it --rm --name rabbitmq -p 5672:5672 rabbitmq +---- + +Launch "essential" tests (takes about 10 minutes): + +[source,shell] +---- +./mvnw verify \ + -Drabbitmqctl.bin=DOCKER:rabbitmq \ + -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite +---- + +Launch a single test: + +[source,shell] +---- +./mvnw verify \ + -Drabbitmqctl.bin=DOCKER:rabbitmq \ + -Dit.test=DeadLetterExchange +---- + +=== Launching Tests with a Local Broker + +The tests can run against a local broker as well. The `rabbitmqctl.bin` +system property must point to the `rabbitmqctl` program: + +[source,shell] +---- +./mvnw verify \ + -Dtest-broker.A.nodename=rabbit@$(hostname) \ + -Drabbitmqctl.bin=/path/to/rabbitmqctl \ + -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite +---- + +To launch a single test: + +[source,shell] +---- +./mvnw verify \ + -Dtest-broker.A.nodename=rabbit@$(hostname) \ + -Drabbitmqctl.bin=/path/to/rabbitmqctl \ + -Dit.test=DeadLetterExchange +---- + +== Contributing + +See link:CONTRIBUTING.md[Contributing] and link:RUNNING_TESTS.md[How to Run Tests]. + +== Versioning + +This library uses https://semver.org/[semantic versioning]. + +== Support + +See the https://www.rabbitmq.com/client-libraries/java-versions[RabbitMQ Java libraries support page] +for the support timeline of this library. + +== License + +This package, the RabbitMQ Java client library, is https://www.rabbitmq.com/client-libraries/java-api-guide#license[triple-licensed] under +the Mozilla Public License 2.0 ("MPL"), the GNU General Public License +version 2 ("GPL") and the Apache License version 2 ("AL"). + +This means that the user can consider the library to be licensed under **any of the licenses from the list** above. +For example, you may choose the Apache Public License 2.0 and include this client into a commercial product. +Projects that are licensed under the GPLv2 may choose GPLv2, and so on. diff --git a/README.in b/README.in deleted file mode 100644 index e9ccd32f24..0000000000 --- a/README.in +++ /dev/null @@ -1,12 +0,0 @@ -Please see http://www.rabbitmq.com/build-java-client.html for build -instructions. - -For your convenience, a text copy of these instructions is available -below. Please be aware that the instructions here may not be as up to -date as those at the above URL. - -See LICENSE for license information. - -=========================================================================== - - diff --git a/README.md b/README.md deleted file mode 100644 index 0aedfa732a..0000000000 --- a/README.md +++ /dev/null @@ -1,37 +0,0 @@ -## RabbitMQ Java Client - -This repository contains source code of the [RabbitMQ Java client](http://www.rabbitmq.com/api-guide.html). -The client is maintained by the [RabbitMQ team at Pivotal](http://github.com/rabbitmq/). - - -## Dependency (Maven Artifact) - -Maven artifacts are [released to Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.rabbitmq%20a%3Aamqp-client). - -### Maven - -``` xml - - com.rabbitmq - amqp-client - 3.4.4 - -``` - -### Gradle - -``` groovy -compile 'com.rabbitmq:amqp-client:3.4.4' -``` - - -## Contributing - -See [Contributing](./CONTRIBUTING.md) and [How to Run Tests](./RUNNING_TESTS.md). - - -## License - -This package, the RabbitMQ Java client library, is triple-licensed under -the Mozilla Public License 1.1 ("MPL"), the GNU General Public License -version 2 ("GPL") and the Apache License version 2 ("ASL"). diff --git a/RUNNING_TESTS.md b/RUNNING_TESTS.md index 2eb0b79567..f948127ba1 100644 --- a/RUNNING_TESTS.md +++ b/RUNNING_TESTS.md @@ -1,101 +1,110 @@ -## Overview +# Running RabbitMQ Java Client Test Suites There are multiple test suites in the RabbitMQ Java client library; -the source for all of the suites can be found in the [test/src](./test/src) +the source for all of the suites can be found in the [src/test/java](src/test/java) directory. The suites are: * Client tests - * Functional tests * Server tests - * SSL tests + * TLS connectivity tests + * Functional tests + * Multi-node tests -All of them assume a RabbitMQ node listening on localhost:5672 -(the default settings). SSL tests require a broker listening on the default -SSL port. Connection recovery tests assume `rabbitmqctl` at `../rabbitmq-server/scripts/rabbitmqctl` -can control the running node: this is the case when all repositories are cloned using -the [umbrella repository](https://github.com/rabbitmq/rabbitmq-public-umbrella). +All of them assume a RabbitMQ node listening on `localhost:5672` +(the default settings). TLS tests require a broker listening on the default +TLS port, `5671`. Multi-node tests expect a second cluster node listening on `localhost:5673`. -For details on running specific tests, see below. +Connection recovery tests need `rabbitmqctl` to control the running nodes. +Note running all those tests requires a fairly complicated setup and is overkill +for most contributions. This is why this document will cover how to run the most +important subset of the test suite. Continuous integration jobs run the whole test +suite anyway. -## Running a Specific Test Suite +## Running Tests -To run a specific test suite you should execute one of the following in the -top-level directory of the source tree: +Use `make deps` to fetch the dependencies in the `deps` directory: + +``` +make deps +``` + +To run a subset of the test suite (do not forget to start a local RabbitMQ node): + +``` +./mvnw verify \ + -Dtest-broker.A.nodename=rabbit@$(hostname) \ + -Drabbitmqctl.bin=/path/to/rabbitmqctl \ + -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite +``` - # runs unit tests - ant test-client - # runs integration/functional tests - ant test-functional - # runs TLS tests - ant test-ssl - # run all test suites - ant test-suite +The test suite subset does not include TLS tests, which is fine for most +contributions and makes the setup easier. -For example, to run the client tests: +The previous command launches tests against the blocking IO connector. +To run the tests against the NIO connector, add `-P use-nio` to the command line: ``` ------------------ Example shell session ------------------------------------- -rabbitmq-java-client$ ant test-client -Buildfile: build.xml +./mvnw verify -P use-nio \ + -Dtest-broker.A.nodename=rabbit@$(hostname) \ + -Drabbitmqctl.bin=/path/to/rabbitmqctl \ + -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite +``` -test-prepare: +For details on running specific tests, see below. -test-build: -amqp-generate-check: +## Running a Specific Test Suite -amqp-generate: +To run a specific test suite, execute one of the following in the +top-level directory of the source tree: -build: +* To run the client unit tests: -test-build-param: +``` +./mvnw verify -P use-nio \ + -Dtest-broker.A.nodename=rabbit@$(hostname) \ + -Drabbitmqctl.bin=/path/to/rabbitmqctl \ + -Dit.test=ClientTestSuite +``` -test-client: - [junit] Running com.rabbitmq.client.test.ClientTests - [junit] Tests run: 31, Failures: 0, Errors: 0, Time elapsed: 2.388 sec +* To run the functional tests: -BUILD SUCCESSFUL ------------------------------------------------------------------------------ ``` +./mvnw verify -P use-nio \ + -Dtest-broker.A.nodename=rabbit@$(hostname) \ + -Drabbitmqctl.bin=/path/to/rabbitmqctl \ + -Dit.test=FunctionalTestSuite +``` + +* To run a single test: -Test failures and errors are logged to `build/TEST-*`. +``` +./mvnw verify -P use-nio \ + -Dtest-broker.A.nodename=rabbit@$(hostname) \ + -Drabbitmqctl.bin=/path/to/rabbitmqctl \ + -Dit.test=DeadLetterExchange +``` +Test reports can be found in `target/failsafe-reports`. -## SSL Test Setup +## Running Against a Broker in a Docker Container -To run the SSL tests, the RabbitMQ server and Java client should be configured -as per the SSL instructions on the RabbitMQ website. The `SSL_CERTS_DIR` -environment variable must point to a certificate folder with the following -minimal structure: +Run the broker: ``` - $SSL_CERTS_DIR - |-- client - | |-- keycert.p12 - | |-- cert.pem - | \-- key.pem - |-- server - | |-- cert.pem - | \-- key.pem - \-- testca - \-- cacert.pem +docker run -it --rm --name rabbitmq -p 5672:5672 rabbitmq:3.8 ``` -The `PASSWORD` environment variable must be set to the password of the keycert.p12 -PKCS12 keystore. The broker must be configured to validate client certificates. -This will become minimal broker configuration file if `$SSL_CERTS_DIR` is replaced -with the certificate folder: +Launch the tests: -``` erlang -%%%%% begin sample test broker configuration -[{rabbit, [{ssl_listeners, [5671]}, - {ssl_options, [{cacertfile,"$SSL_CERTS_DIR/testca/cacert.pem"}, - {certfile,"$SSL_CERTS_DIR/server/cert.pem"}, - {keyfile,"$SSL_CERTS_DIR/server/key.pem"}, - {verify,verify_peer}, - {fail_if_no_peer_cert, false}]}]}]. -%%%%% end sample test broker configuration ``` +./mvnw verify \ + -Drabbitmqctl.bin=DOCKER:rabbitmq \ + -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite +``` + +Note the `rabbitmqctl.bin` system property uses the syntax +`DOCKER:{containerId}`. diff --git a/build.properties b/build.properties deleted file mode 100644 index b113e0e832..0000000000 --- a/build.properties +++ /dev/null @@ -1,21 +0,0 @@ -alt.javac.source=1.6 -alt.javac.target=1.6 -build.out=build -bundle.name=RabbitMQ Java AMQP client library -bundle.out=${build.out}/bundle -bundle.symbolicName=com.rabbitmq.client -bundlor.home=bundlor -dist.out=${build.out}/dist -impl.version=0.0.0 -javac.debug=true -javac.out=${build.out}/classes -javadoc.out=build/doc/api -lib.out=${build.out}/lib -python.bin=python -sibling.codegen.dir=../rabbitmq-codegen/ -spec.version=0.9.1 -src.generated=${build.out}/gensrc -standard.javac.source=1.6 -standard.javac.target=1.6 -test.javac.out=${build.out}/test/classes -test.src.home=test/src diff --git a/build.xml b/build.xml deleted file mode 100644 index d1cee7636c..0000000000 --- a/build.xml +++ /dev/null @@ -1,511 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/bundlor/dist/com.springsource.bundlor-1.0.0.RELEASE.jar b/bundlor/dist/com.springsource.bundlor-1.0.0.RELEASE.jar deleted file mode 100644 index e8a8fa37ef..0000000000 Binary files a/bundlor/dist/com.springsource.bundlor-1.0.0.RELEASE.jar and /dev/null differ diff --git a/bundlor/dist/com.springsource.bundlor.ant-1.0.0.RELEASE.jar b/bundlor/dist/com.springsource.bundlor.ant-1.0.0.RELEASE.jar deleted file mode 100644 index f56eb8e38c..0000000000 Binary files a/bundlor/dist/com.springsource.bundlor.ant-1.0.0.RELEASE.jar and /dev/null differ diff --git a/bundlor/dist/com.springsource.bundlor.blint-1.0.0.RELEASE.jar b/bundlor/dist/com.springsource.bundlor.blint-1.0.0.RELEASE.jar deleted file mode 100644 index 39232af1c0..0000000000 Binary files a/bundlor/dist/com.springsource.bundlor.blint-1.0.0.RELEASE.jar and /dev/null differ diff --git a/bundlor/dist/com.springsource.bundlor.commandline-1.0.0.RELEASE.jar b/bundlor/dist/com.springsource.bundlor.commandline-1.0.0.RELEASE.jar deleted file mode 100644 index d2895bf8f3..0000000000 Binary files a/bundlor/dist/com.springsource.bundlor.commandline-1.0.0.RELEASE.jar and /dev/null differ diff --git a/bundlor/lib/com.springsource.org.apache.commons.cli-1.2.0.jar b/bundlor/lib/com.springsource.org.apache.commons.cli-1.2.0.jar deleted file mode 100644 index 48d0c86f26..0000000000 Binary files a/bundlor/lib/com.springsource.org.apache.commons.cli-1.2.0.jar and /dev/null differ diff --git a/bundlor/lib/com.springsource.org.objectweb.asm-3.1.0.jar b/bundlor/lib/com.springsource.org.objectweb.asm-3.1.0.jar deleted file mode 100644 index da07fa0376..0000000000 Binary files a/bundlor/lib/com.springsource.org.objectweb.asm-3.1.0.jar and /dev/null differ diff --git a/bundlor/lib/com.springsource.org.objectweb.asm.commons-3.1.0.jar b/bundlor/lib/com.springsource.org.objectweb.asm.commons-3.1.0.jar deleted file mode 100644 index 14d55b7083..0000000000 Binary files a/bundlor/lib/com.springsource.org.objectweb.asm.commons-3.1.0.jar and /dev/null differ diff --git a/bundlor/lib/com.springsource.org.objectweb.asm.tree-3.1.0.jar b/bundlor/lib/com.springsource.org.objectweb.asm.tree-3.1.0.jar deleted file mode 100644 index 7b5440b943..0000000000 Binary files a/bundlor/lib/com.springsource.org.objectweb.asm.tree-3.1.0.jar and /dev/null differ diff --git a/bundlor/lib/com.springsource.util.common-2.0.0.RELEASE.jar b/bundlor/lib/com.springsource.util.common-2.0.0.RELEASE.jar deleted file mode 100644 index 22d4559778..0000000000 Binary files a/bundlor/lib/com.springsource.util.common-2.0.0.RELEASE.jar and /dev/null differ diff --git a/bundlor/lib/com.springsource.util.math-2.0.0.RELEASE.jar b/bundlor/lib/com.springsource.util.math-2.0.0.RELEASE.jar deleted file mode 100644 index 16ca699616..0000000000 Binary files a/bundlor/lib/com.springsource.util.math-2.0.0.RELEASE.jar and /dev/null differ diff --git a/bundlor/lib/com.springsource.util.osgi-2.0.0.RELEASE.jar b/bundlor/lib/com.springsource.util.osgi-2.0.0.RELEASE.jar deleted file mode 100644 index b63f391ab0..0000000000 Binary files a/bundlor/lib/com.springsource.util.osgi-2.0.0.RELEASE.jar and /dev/null differ diff --git a/bundlor/lib/com.springsource.util.parser.manifest-2.0.0.RELEASE.jar b/bundlor/lib/com.springsource.util.parser.manifest-2.0.0.RELEASE.jar deleted file mode 100644 index 0119f91e70..0000000000 Binary files a/bundlor/lib/com.springsource.util.parser.manifest-2.0.0.RELEASE.jar and /dev/null differ diff --git a/bundlor/lib/org.osgi.core-4.1.0.jar b/bundlor/lib/org.osgi.core-4.1.0.jar deleted file mode 100644 index 2d652ac647..0000000000 Binary files a/bundlor/lib/org.osgi.core-4.1.0.jar and /dev/null differ diff --git a/bundlorTemplate.mf b/bundlorTemplate.mf deleted file mode 100644 index 7647d07d9e..0000000000 --- a/bundlorTemplate.mf +++ /dev/null @@ -1,7 +0,0 @@ -Bundle-ManifestVersion: 2 -Bundle-SymbolicName: ${bundle.symbolicName} -Bundle-Name: ${bundle.name} -Bundle-Vendor: SpringSource -Bundle-Version: ${impl.version} -Import-Template: - javax.*;version=0 diff --git a/bundlorTestTemplate.mf b/bundlorTestTemplate.mf deleted file mode 100644 index 98b6d800f3..0000000000 --- a/bundlorTestTemplate.mf +++ /dev/null @@ -1,11 +0,0 @@ -Bundle-ManifestVersion: 2 -Bundle-SymbolicName: ${bundle.symbolicName} -Bundle-Name: ${bundle.name} -Bundle-Vendor: SpringSource -Bundle-Version: ${impl.version} -Import-Template: - org.apache.commons.io.*;version="[1.2,2)", - org.apache.commons.cli.*;version="[1.1,2)", - com.rabbitmq.*;version="${impl.version:[=.=.=,=.+1)}", - junit.*;version="[4,5)", - javax.*;version=0 diff --git a/ci/_start-cluster.sh b/ci/_start-cluster.sh new file mode 100755 index 0000000000..6b01992d96 --- /dev/null +++ b/ci/_start-cluster.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +LOCAL_SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.0} + +wait_for_message() { + while ! docker logs "$1" | grep -q "$2"; + do + sleep 5 + echo "Waiting 5 seconds for $1 to start..." + done +} + +make -C "${PWD}"/tls-gen/basic + +mv tls-gen/basic/result/server_$(hostname -s)_certificate.pem tls-gen/basic/result/server_certificate.pem +mv tls-gen/basic/result/server_$(hostname -s)_key.pem tls-gen/basic/result/server_key.pem +mv tls-gen/basic/server_$(hostname -s) tls-gen/basic/server +mv tls-gen/basic/client_$(hostname -s) tls-gen/basic/client + +rm -rf rabbitmq-configuration +mkdir -p rabbitmq-configuration/tls + +cp -R "${PWD}"/tls-gen/basic/* rabbitmq-configuration/tls +chmod -R o+r rabbitmq-configuration/tls/* +chmod -R g+r rabbitmq-configuration/tls/* +./mvnw -q clean resources:testResources -Dtest-tls-certs.dir=/etc/rabbitmq/tls +cp target/test-classes/rabbit@localhost.config rabbitmq-configuration/rabbit@localhost.config +cp target/test-classes/hare@localhost.config rabbitmq-configuration/hare@localhost.config + +echo "Running RabbitMQ ${RABBITMQ_IMAGE}" + +docker rm -f rabbitmq 2>/dev/null || echo "rabbitmq was not running" +docker run -d --name rabbitmq \ + --network host \ + -v "${PWD}"/rabbitmq-configuration:/etc/rabbitmq \ + --env RABBITMQ_CONFIG_FILE=/etc/rabbitmq/rabbit@localhost.config \ + --env RABBITMQ_NODENAME=rabbit@$(hostname) \ + --env RABBITMQ_NODE_PORT=5672 \ + --env RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-setcookie do-not-do-this-in-production" \ + "${RABBITMQ_IMAGE}" + +# for CLI commands to share the same cookie +docker exec rabbitmq bash -c "echo 'do-not-do-this-in-production' > /var/lib/rabbitmq/.erlang.cookie" +docker exec rabbitmq chmod 0600 /var/lib/rabbitmq/.erlang.cookie + +wait_for_message rabbitmq "completed with" + +docker run -d --name hare \ + --network host \ + -v "${PWD}"/rabbitmq-configuration:/etc/rabbitmq \ + --env RABBITMQ_CONFIG_FILE=/etc/rabbitmq/hare@localhost.config \ + --env RABBITMQ_NODENAME=hare@$(hostname) \ + --env RABBITMQ_NODE_PORT=5673 \ + --env RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-setcookie do-not-do-this-in-production" \ + "${RABBITMQ_IMAGE}" + +# for CLI commands to share the same cookie +docker exec hare bash -c "echo 'do-not-do-this-in-production' > /var/lib/rabbitmq/.erlang.cookie" +docker exec hare chmod 0600 /var/lib/rabbitmq/.erlang.cookie + +wait_for_message hare "completed with" + +docker exec hare rabbitmqctl --node hare@$(hostname) status + +docker exec rabbitmq rabbitmq-diagnostics --node rabbit@$(hostname) is_running +docker exec hare rabbitmq-diagnostics --node hare@$(hostname) is_running + +docker exec hare rabbitmqctl --node hare@$(hostname) stop_app +docker exec hare rabbitmqctl --node hare@$(hostname) join_cluster rabbit@$(hostname) +docker exec hare rabbitmqctl --node hare@$(hostname) start_app + +sleep 10 + +docker exec hare rabbitmqctl --node hare@$(hostname) await_startup + +docker exec hare rabbitmqctl --node hare@$(hostname) enable_feature_flag --opt-in khepri_db +docker exec rabbitmq rabbitmqctl --node rabbit@$(hostname) enable_feature_flag --opt-in khepri_db + +docker exec rabbitmq rabbitmq-diagnostics --node rabbit@$(hostname) erlang_version +docker exec rabbitmq rabbitmqctl --node rabbit@$(hostname) version +docker exec rabbitmq rabbitmqctl --node rabbit@$(hostname) status +docker exec rabbitmq rabbitmqctl --node rabbit@$(hostname) cluster_status diff --git a/ci/cluster/configuration/rabbitmq.conf b/ci/cluster/configuration/rabbitmq.conf new file mode 100644 index 0000000000..652395d768 --- /dev/null +++ b/ci/cluster/configuration/rabbitmq.conf @@ -0,0 +1,20 @@ +cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config +cluster_formation.classic_config.nodes.1 = rabbit@node0 +cluster_formation.classic_config.nodes.2 = rabbit@node1 +cluster_formation.classic_config.nodes.3 = rabbit@node2 +loopback_users = none + +listeners.ssl.default = 5671 + +ssl_options.cacertfile = /etc/rabbitmq/tls/ca_certificate.pem +ssl_options.certfile = /etc/rabbitmq/tls/server_certificate.pem +ssl_options.keyfile = /etc/rabbitmq/tls/server_key.pem +ssl_options.verify = verify_peer +ssl_options.fail_if_no_peer_cert = false +ssl_options.honor_cipher_order = true + +auth_mechanisms.1 = PLAIN +auth_mechanisms.2 = ANONYMOUS +auth_mechanisms.3 = AMQPLAIN +auth_mechanisms.4 = EXTERNAL +auth_mechanisms.5 = RABBIT-CR-DEMO diff --git a/ci/cluster/docker-compose.yml b/ci/cluster/docker-compose.yml new file mode 100644 index 0000000000..cf0dd75c54 --- /dev/null +++ b/ci/cluster/docker-compose.yml @@ -0,0 +1,49 @@ +services: + node0: + environment: + - RABBITMQ_ERLANG_COOKIE='secret_cookie' + networks: + - rabbitmq-cluster + hostname: node0 + container_name: rabbitmq0 + image: ${RABBITMQ_IMAGE:-rabbitmq:4.1} + pull_policy: always + ports: + - "5672:5672" + - "5671:5671" + tty: true + volumes: + - ./configuration/:/etc/rabbitmq/ + - ../../rabbitmq-configuration/tls:/etc/rabbitmq/tls/ + node1: + environment: + - RABBITMQ_ERLANG_COOKIE='secret_cookie' + networks: + - rabbitmq-cluster + hostname: node1 + container_name: rabbitmq1 + image: ${RABBITMQ_IMAGE:-rabbitmq:4.1} + pull_policy: always + ports: + - "5673:5672" + tty: true + volumes: + - ./configuration/:/etc/rabbitmq/ + - ../../rabbitmq-configuration/tls:/etc/rabbitmq/tls/ + node2: + environment: + - RABBITMQ_ERLANG_COOKIE='secret_cookie' + networks: + - rabbitmq-cluster + hostname: node2 + container_name: rabbitmq2 + image: ${RABBITMQ_IMAGE:-rabbitmq:4.1} + pull_policy: always + ports: + - "5674:5672" + tty: true + volumes: + - ./configuration/:/etc/rabbitmq/ + - ../../rabbitmq-configuration/tls:/etc/rabbitmq/tls/ +networks: + rabbitmq-cluster: diff --git a/ci/evaluate-release.sh b/ci/evaluate-release.sh new file mode 100755 index 0000000000..4ad656d7a0 --- /dev/null +++ b/ci/evaluate-release.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +source ./release-versions.txt + +if [[ $RELEASE_VERSION == *[RCM]* ]] +then + echo "prerelease=true" >> $GITHUB_ENV + echo "ga_release=false" >> $GITHUB_ENV + echo "maven_server_id=packagecloud-rabbitmq-maven-milestones" >> $GITHUB_ENV +else + echo "prerelease=false" >> $GITHUB_ENV + echo "ga_release=true" >> $GITHUB_ENV + echo "maven_server_id=ossrh" >> $GITHUB_ENV +fi \ No newline at end of file diff --git a/ci/release-java-client.sh b/ci/release-java-client.sh new file mode 100755 index 0000000000..d3fd7df952 --- /dev/null +++ b/ci/release-java-client.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +source ./release-versions.txt +git checkout $RELEASE_BRANCH + +./mvnw release:clean release:prepare -DdryRun=true -Darguments="-DskipTests" --no-transfer-progress \ + --batch-mode -Dtag="v$RELEASE_VERSION" \ + -DreleaseVersion=$RELEASE_VERSION \ + -DdevelopmentVersion=$DEVELOPMENT_VERSION \ + +./mvnw release:clean release:prepare -Darguments="-DskipTests" --no-transfer-progress \ + --batch-mode -Dtag="v$RELEASE_VERSION" \ + -DreleaseVersion=$RELEASE_VERSION \ + -DdevelopmentVersion=$DEVELOPMENT_VERSION + +git checkout "v$RELEASE_VERSION" + +if [[ $RELEASE_VERSION == *[RCM]* ]] +then + MAVEN_PROFILE="milestone" + echo "prerelease=true" >> $GITHUB_ENV +else + MAVEN_PROFILE="release" + echo "prerelease=false" >> $GITHUB_ENV +fi + +./mvnw clean deploy -P $MAVEN_PROFILE -DskipTests --no-transfer-progress \ No newline at end of file diff --git a/ci/start-broker.sh b/ci/start-broker.sh new file mode 100755 index 0000000000..a73a08f270 --- /dev/null +++ b/ci/start-broker.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +LOCAL_SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.1} + +wait_for_message() { + while ! docker logs "$1" | grep -q "$2"; + do + sleep 5 + echo "Waiting 5 seconds for $1 to start..." + done +} + +rm -rf rabbitmq-configuration +mkdir -p rabbitmq-configuration/tls + +make -C "${PWD}"/tls-gen/basic + +rm -rf rabbitmq-configuration +mkdir -p rabbitmq-configuration/tls +cp -R "${PWD}"/tls-gen/basic/result/* rabbitmq-configuration/tls +chmod o+r rabbitmq-configuration/tls/* +chmod g+r rabbitmq-configuration/tls/* + +echo "loopback_users = none + +listeners.ssl.default = 5671 + +ssl_options.cacertfile = /etc/rabbitmq/tls/ca_certificate.pem +ssl_options.certfile = /etc/rabbitmq/tls/server_$(hostname)_certificate.pem +ssl_options.keyfile = /etc/rabbitmq/tls/server_$(hostname)_key.pem +ssl_options.verify = verify_peer +ssl_options.fail_if_no_peer_cert = false +ssl_options.honor_cipher_order = true + +auth_mechanisms.1 = PLAIN +auth_mechanisms.2 = ANONYMOUS +auth_mechanisms.3 = AMQPLAIN +auth_mechanisms.4 = EXTERNAL +auth_mechanisms.5 = RABBIT-CR-DEMO" >> rabbitmq-configuration/rabbitmq.conf + +echo "Running RabbitMQ ${RABBITMQ_IMAGE}" + +docker rm -f rabbitmq 2>/dev/null || echo "rabbitmq was not running" +docker run -d --name rabbitmq \ + --network host \ + -v "${PWD}"/rabbitmq-configuration:/etc/rabbitmq \ + "${RABBITMQ_IMAGE}" + +wait_for_message rabbitmq "completed with" + +docker exec rabbitmq rabbitmqctl enable_feature_flag --opt-in khepri_db +docker exec rabbitmq rabbitmq-diagnostics erlang_version +docker exec rabbitmq rabbitmqctl version diff --git a/ci/start-cluster.sh b/ci/start-cluster.sh new file mode 100755 index 0000000000..6a5042c098 --- /dev/null +++ b/ci/start-cluster.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +export RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.1} + +wait_for_message() { + while ! docker logs "$1" | grep -q "$2"; + do + sleep 2 + echo "Waiting 2 seconds for $1 to start..." + done +} + +rm -rf rabbitmq-configuration +mkdir -p rabbitmq-configuration/tls + +make -C "${PWD}"/tls-gen/basic + +rm -rf rabbitmq-configuration +mkdir -p rabbitmq-configuration/tls +cp -R "${PWD}"/tls-gen/basic/result/* rabbitmq-configuration/tls +mv rabbitmq-configuration/tls/server_$(hostname)_certificate.pem rabbitmq-configuration/tls/server_certificate.pem +mv rabbitmq-configuration/tls/server_$(hostname)_key.pem rabbitmq-configuration/tls/server_key.pem +chmod o+r rabbitmq-configuration/tls/* +chmod g+r rabbitmq-configuration/tls/* + +docker compose --file ci/cluster/docker-compose.yml down +docker compose --file ci/cluster/docker-compose.yml up --detach + +wait_for_message rabbitmq0 "completed with" + +docker exec rabbitmq0 rabbitmqctl await_online_nodes 3 + +docker exec rabbitmq0 rabbitmqctl enable_feature_flag --opt-in khepri_db +docker exec rabbitmq1 rabbitmqctl enable_feature_flag --opt-in khepri_db +docker exec rabbitmq2 rabbitmqctl enable_feature_flag --opt-in khepri_db + +docker exec rabbitmq0 rabbitmqctl cluster_status + +docker compose --file ci/cluster/docker-compose.yml ps diff --git a/codegen.py b/codegen.py old mode 100644 new mode 100755 index 24e2e52364..81b2694fea --- a/codegen.py +++ b/codegen.py @@ -1,26 +1,26 @@ -## The contents of this file are subject to the Mozilla Public License -## Version 1.1 (the "License"); you may not use this file except in -## compliance with the License. You may obtain a copy of the License -## at http://www.mozilla.org/MPL/ -## -## Software distributed under the License is distributed on an "AS IS" -## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -## the License for the specific language governing rights and -## limitations under the License. +#!/usr/bin/env python + +## Copyright (c) 2007-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. ## -## The Original Code is RabbitMQ. +## This software, the RabbitMQ Java client library, is triple-licensed under the +## Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +## ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +## LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +## please see LICENSE-APACHE2. ## -## The Initial Developer of the Original Code is GoPivotal, Inc. -## Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +## This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +## either express or implied. See the LICENSE file for specific language governing +## rights and limitations of this software. ## +## If you have any questions regarding licensing, please contact us at +## info@rabbitmq.com. from __future__ import nested_scopes +from __future__ import print_function + import re import sys -sys.path.append("../rabbitmq-codegen") # in case we're next to an experimental revision -sys.path.append("codegen") # in case we're building from a distribution package - from amqp_codegen import * class BogusDefaultValue(Exception): @@ -127,251 +127,254 @@ def nullCheckedFields(spec, m): #--------------------------------------------------------------------------- def printFileHeader(): - print """// NOTE: This -*- java -*- source code is autogenerated from the AMQP + print("""// NOTE: This -*- java -*- source code is autogenerated from the AMQP // specification! // -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. // -""" +""") def genJavaApi(spec): def printHeader(): printFileHeader() - print "package com.rabbitmq.client;" - print - print "import java.io.DataInputStream;" - print "import java.io.IOException;" - print "import java.util.Collections;" - print "import java.util.HashMap;" - print "import java.util.Map;" - print "import java.util.Date;" - print - print "import com.rabbitmq.client.impl.ContentHeaderPropertyWriter;" - print "import com.rabbitmq.client.impl.ContentHeaderPropertyReader;" - print "import com.rabbitmq.client.impl.LongStringHelper;" + print("package com.rabbitmq.client;") + print() + print("import java.io.DataInputStream;") + print("import java.io.IOException;") + print("import java.util.Collections;") + print("import java.util.HashMap;") + print("import java.util.Map;") + print("import java.util.Date;") + print() + print("import com.rabbitmq.client.impl.ContentHeaderPropertyWriter;") + print("import com.rabbitmq.client.impl.ContentHeaderPropertyReader;") + print("import com.rabbitmq.client.impl.LongStringHelper;") def printProtocolClass(): - print - print " public static class PROTOCOL {" - print " public static final int MAJOR = %i;" % spec.major - print " public static final int MINOR = %i;" % spec.minor - print " public static final int REVISION = %i;" % spec.revision - print " public static final int PORT = %i;" % spec.port - print " }" + print() + print(" public static class PROTOCOL {") + print(" public static final int MAJOR = %i;" % spec.major) + print(" public static final int MINOR = %i;" % spec.minor) + print(" public static final int REVISION = %i;" % spec.revision) + print(" public static final int PORT = %i;" % spec.port) + print(" }") def printConstants(): - print - for (c,v,cls) in spec.constants: print " public static final int %s = %i;" % (java_constant_name(c), v) + print() + for (c,v,cls) in spec.constants: print(" public static final int %s = %i;" % (java_constant_name(c), v)) def builder(c,m): def ctorCall(c,m): ctor_call = "com.rabbitmq.client.impl.AMQImpl.%s.%s" % (java_class_name(c.name),java_class_name(m.name)) ctor_arg_list = [ java_field_name(a.name) for a in m.arguments ] - print " return new %s(%s);" % (ctor_call, ", ".join(ctor_arg_list)) + print(" return new %s(%s);" % (ctor_call, ", ".join(ctor_arg_list))) def genFields(spec, m): for a in m.arguments: (jfType, jfName, jfDefault) = typeNameDefault(spec, a) if a.defaultvalue != None: - print " private %s %s = %s;" % (jfType, jfName, jfDefault) + print(" private %s %s = %s;" % (jfType, jfName, jfDefault)) else: - print " private %s %s;" % (jfType, jfName) + print(" private %s %s;" % (jfType, jfName)) def genArgMethods(spec, m): for a in m.arguments: (jfType, jfName, jfDefault) = typeNameDefault(spec, a) - print " public Builder %s(%s %s)" % (jfName, jfType, jfName) - print " { this.%s = %s; return this; }" % (jfName, jfName) + print(" public Builder %s(%s %s)" % (jfName, jfType, jfName)) + print(" { this.%s = %s; return this; }" % (jfName, jfName)) if jfType == "boolean": - print " public Builder %s()" % (jfName) - print " { return this.%s(true); }" % (jfName) + print(" public Builder %s()" % (jfName)) + print(" { return this.%s(true); }" % (jfName)) elif jfType == "LongString": - print " public Builder %s(String %s)" % (jfName, jfName) - print " { return this.%s(LongStringHelper.asLongString(%s)); }" % (jfName, jfName) + print(" public Builder %s(String %s)" % (jfName, jfName)) + print(" { return this.%s(LongStringHelper.asLongString(%s)); }" % (jfName, jfName)) def genBuildMethod(c,m): - print " public %s build() {" % (java_class_name(m.name)) + print(" public %s build() {" % (java_class_name(m.name))) ctorCall(c,m) - print " }" + print(" }") - print - print " // Builder for instances of %s.%s" % (java_class_name(c.name), java_class_name(m.name)) - print " public static final class Builder" - print " {" + print() + print(" // Builder for instances of %s.%s" % (java_class_name(c.name), java_class_name(m.name))) + print(" public static final class Builder") + print(" {") genFields(spec, m) - print - print " public Builder() { }" - print + print() + print(" public Builder() { }") + print() genArgMethods(spec, m) genBuildMethod(c,m) - print " }" + print(" }") def printClassInterfaces(): for c in spec.classes: - print - print " public static class %s {" % (java_class_name(c.name)) + print() + print(" public static class %s {" % (java_class_name(c.name))) for m in c.allMethods(): - print " public interface %s extends Method {" % ((java_class_name(m.name))) + print(" public interface %s extends Method {" % ((java_class_name(m.name)))) for a in m.arguments: - print " %s %s();" % (java_field_type(spec, a.domain), java_getter_name(a.name)) + print(" %s %s();" % (java_field_type(spec, a.domain), java_getter_name(a.name))) builder(c,m) - print " }" - print " }" + print(" }") + print(" }") def printReadProperties(c): if c.fields: for f in c.fields: - print " boolean %s_present = reader.readPresence();" % (java_field_name(f.name)) - print + print(" boolean %s_present = reader.readPresence();" % (java_field_name(f.name))) + print() - print " reader.finishPresence();" + print(" reader.finishPresence();") if c.fields: - print + print() for f in c.fields: (jfName, jfClass) = (java_field_name(f.name), java_class_name(f.domain)) - print " this.%s = %s_present ? reader.read%s() : null;" % (jfName, jfName, jfClass) + print(" this.%s = %s_present ? reader.read%s() : null;" % (jfName, jfName, jfClass)) def printWritePropertiesTo(c): - print - print " public void writePropertiesTo(ContentHeaderPropertyWriter writer)" - print " throws IOException" - print " {" + print() + print(" public void writePropertiesTo(ContentHeaderPropertyWriter writer)") + print(" throws IOException") + print(" {") if c.fields: for f in c.fields: - print " writer.writePresence(this.%s != null);" % (java_field_name(f.name)) - print - print " writer.finishPresence();" + print(" writer.writePresence(this.%s != null);" % (java_field_name(f.name))) + print() + print(" writer.finishPresence();") if c.fields: - print + print() for f in c.fields: (jfName, jfClass) = (java_field_name(f.name), java_class_name(f.domain)) - print " if (this.%s != null) writer.write%s(this.%s);" % (jfName, jfClass, jfName) - print " }" + print(" if (this.%s != null) writer.write%s(this.%s);" % (jfName, jfClass, jfName)) + print(" }") def printAppendPropertyDebugStringTo(c): appendList = [ "%s=\")\n .append(this.%s)\n .append(\"" % (f.name, java_field_name(f.name)) for f in c.fields ] - print - print " public void appendPropertyDebugStringTo(StringBuilder acc) {" - print " acc.append(\"(%s)\");" % (", ".join(appendList)) - print " }" + print() + print(" public void appendPropertyDebugStringTo(StringBuilder acc) {") + print(" acc.append(\"(%s)\");" % (", ".join(appendList))) + print(" }") def printPropertiesBuilderClass(c): def printBuilderSetter(fieldType, fieldName): - print " public Builder %s(%s %s)" % (fieldName, java_boxed_type(fieldType), fieldName) - print " { this.%s = %s; return this; }" % (fieldName, fieldName) + print(" public Builder %s(%s %s)" % (fieldName, java_boxed_type(fieldType), fieldName)) + print(" { this.%s = %s; return this; }" % (fieldName, fieldName)) if fieldType == "boolean": - print " public Builder %s()" % (fieldName) - print " { return this.%s(true); }" % (fieldName) + print(" public Builder %s()" % (fieldName)) + print(" { return this.%s(true); }" % (fieldName)) elif fieldType == "LongString": - print " public Builder %s(String %s)" % (fieldName, fieldName) - print " { return this.%s(LongStringHelper.asLongString(%s)); }" % (fieldName, fieldName) + print(" public Builder %s(String %s)" % (fieldName, fieldName)) + print(" { return this.%s(LongStringHelper.asLongString(%s)); }" % (fieldName, fieldName)) - print - print " public static final class Builder {" + print() + print(" public static final class Builder {") # fields for f in c.fields: (fType, fName) = (java_field_type(spec, f.domain), java_field_name(f.name)) - print " private %s %s;" % (java_boxed_type(fType), fName) + print(" private %s %s;" % (java_boxed_type(fType), fName)) # ctor - print - print " public Builder() {};" + print() + print(" public Builder() {};") # setters - print + print() for f in c.fields: printBuilderSetter(java_field_type(spec, f.domain), java_field_name(f.name)) - print + print() jClassName = java_class_name(c.name) # build() objName = "%sProperties" % (jClassName) ctor_parm_list = [ java_field_name(f.name) for f in c.fields ] - print " public %s build() {" % (objName) - print " return new %s" % (objName) - print " ( %s" % ("\n , ".join(ctor_parm_list)) - print " );" - print " }" + print(" public %s build() {" % (objName)) + print(" return new %s" % (objName)) + print(" ( %s" % ("\n , ".join(ctor_parm_list))) + print(" );") + print(" }") - print " }" + print(" }") def printPropertiesBuilder(c): - print - print " public Builder builder() {" - print " Builder builder = new Builder()" + print() + print(" public Builder builder() {") + print(" Builder builder = new Builder()") setFieldList = [ "%s(%s)" % (fn, fn) for fn in [ java_field_name(f.name) for f in c.fields ] ] - print " .%s;" % ("\n .".join(setFieldList)) - print " return builder;" - print " }" + print(" .%s;" % ("\n .".join(setFieldList))) + print(" return builder;") + print(" }") def printPropertiesClass(c): def printGetter(fieldType, fieldName): capFieldName = fieldName[0].upper() + fieldName[1:] - print " public %s get%s() { return this.%s; }" % (java_boxed_type(fieldType), capFieldName, fieldName) + print(" public %s get%s() { return this.%s; }" % (java_boxed_type(fieldType), capFieldName, fieldName)) jClassName = java_class_name(c.name) - print - print " public static class %sProperties extends com.rabbitmq.client.impl.AMQ%sProperties {" % (jClassName, jClassName) + print() + print(" public static class %sProperties extends com.rabbitmq.client.impl.AMQ%sProperties {" % (jClassName, jClassName)) #property fields for f in c.fields: (fType, fName) = (java_boxed_type(java_field_type(spec, f.domain)), java_field_name(f.name)) - print " private %s %s;" % (fType, fName) + print(" private %s %s;" % (fType, fName)) #explicit constructor if c.fields: - print + print() consParmList = [ "%s %s" % (java_boxed_type(java_field_type(spec,f.domain)), java_field_name(f.name)) for f in c.fields ] - print " public %sProperties(" % (jClassName) - print " %s)" % (",\n ".join(consParmList)) - print " {" + print(" public %sProperties(" % (jClassName)) + print(" %s)" % (",\n ".join(consParmList))) + print(" {") for f in c.fields: (fType, fName) = (java_field_type(spec, f.domain), java_field_name(f.name)) if fType == "Map": - print " this.%s = %s==null ? null : Collections.unmodifiableMap(new HashMap(%s));" % (fName, fName, fName) + print(" this.%s = %s==null ? null : Collections.unmodifiableMap(new HashMap(%s));" % (fName, fName, fName)) else: - print " this.%s = %s;" % (fName, fName) - print " }" + print(" this.%s = %s;" % (fName, fName)) + print(" }") #datainputstream constructor - print - print " public %sProperties(DataInputStream in) throws IOException {" % (jClassName) - print " super(in);" - print " ContentHeaderPropertyReader reader = new ContentHeaderPropertyReader(in);" - + print() + print(" public %sProperties(DataInputStream in) throws IOException {" % (jClassName)) + print(" super(in);") + print(" ContentHeaderPropertyReader reader = new ContentHeaderPropertyReader(in);") + printReadProperties(c) - - print " }" + + print(" }") # default constructor - print " public %sProperties() {}" % (jClassName) + print(" public %sProperties() {}" % (jClassName)) #class properties - print " public int getClassId() { return %i; }" % (c.index) - print " public String getClassName() { return \"%s\"; }" % (c.name) + print(" public int getClassId() { return %i; }" % (c.index)) + print(" public String getClassName() { return \"%s\"; }" % (c.name)) + + if c.fields: + equalsHashCode(spec, c.fields, java_class_name(c.name), 'Properties', False) printPropertiesBuilder(c) - + #accessor methods - print + print() for f in c.fields: (jType, jName) = (java_field_type(spec, f.domain), java_field_name(f.name)) printGetter(jType, jName) @@ -380,7 +383,7 @@ def printGetter(fieldType, fieldName): printAppendPropertyDebugStringTo(c) printPropertiesBuilderClass(c) - print " }" + print(" }") def printPropertiesClasses(): for c in spec.classes: @@ -388,81 +391,125 @@ def printPropertiesClasses(): printPropertiesClass(c) printHeader() - print - print "public interface AMQP {" + print() + print("public interface AMQP {") printProtocolClass() printConstants() printClassInterfaces() printPropertiesClasses() - print "}" + print("}") #-------------------------------------------------------------------------------- +def equalsHashCode(spec, fields, jClassName, classSuffix, usePrimitiveType): + print() + print() + print(" @Override") + print(" public boolean equals(Object o) {") + print(" if (this == o)") + print(" return true;") + print(" if (o == null || getClass() != o.getClass())") + print(" return false;") + print(" %s%s that = (%s%s) o;" % (jClassName, classSuffix, jClassName, classSuffix)) + + for f in fields: + (fType, fName) = (java_field_type(spec, f.domain), java_field_name(f.name)) + if usePrimitiveType and fType in javaScalarTypes: + print(" if (%s != that.%s)" % (fName, fName)) + else: + print(" if (%s != null ? !%s.equals(that.%s) : that.%s != null)" % (fName, fName, fName, fName)) + + print(" return false;") + + print(" return true;") + print(" }") + + print() + print(" @Override") + print(" public int hashCode() {") + print(" int result = 0;") + + for f in fields: + (fType, fName) = (java_field_type(spec, f.domain), java_field_name(f.name)) + if usePrimitiveType and fType in javaScalarTypes: + if fType == 'boolean': + print(" result = 31 * result + (%s ? 1 : 0);" % fName) + elif fType == 'long': + print(" result = 31 * result + (int) (%s ^ (%s >>> 32));" % (fName, fName)) + else: + print(" result = 31 * result + %s;" % fName) + else: + print(" result = 31 * result + (%s != null ? %s.hashCode() : 0);" % (fName, fName)) + + print(" return result;") + print(" }") + def genJavaImpl(spec): def printHeader(): printFileHeader() - print "package com.rabbitmq.client.impl;" - print - print "import java.io.IOException;" - print "import java.io.DataInputStream;" - print "import java.util.Collections;" - print "import java.util.HashMap;" - print "import java.util.Map;" - print - print "import com.rabbitmq.client.AMQP;" - print "import com.rabbitmq.client.LongString;" - print "import com.rabbitmq.client.UnknownClassOrMethodId;" - print "import com.rabbitmq.client.UnexpectedMethodError;" + print("package com.rabbitmq.client.impl;") + print() + print("import java.io.IOException;") + print("import java.io.DataInputStream;") + print("import java.util.Collections;") + print("import java.util.HashMap;") + print("import java.util.Map;") + print() + print("import com.rabbitmq.client.AMQP;") + print("import com.rabbitmq.client.LongString;") + print("import com.rabbitmq.client.UnknownClassOrMethodId;") + print("import com.rabbitmq.client.UnexpectedMethodError;") def printClassMethods(spec, c): - print - print " public static class %s {" % (java_class_name(c.name)) - print " public static final int INDEX = %s;" % (c.index) + print() + print(" public static class %s {" % (java_class_name(c.name))) + print(" public static final int INDEX = %s;" % (c.index)) for m in c.allMethods(): def getters(): if m.arguments: - print + print() for a in m.arguments: - print " public %s %s() { return %s; }" % (java_field_type(spec,a.domain), java_getter_name(a.name), java_field_name(a.name)) + print(" public %s %s() { return %s; }" % (java_field_type(spec,a.domain), java_getter_name(a.name), java_field_name(a.name))) def constructors(): - print + print() argList = [ "%s %s" % (java_field_type(spec,a.domain),java_field_name(a.name)) for a in m.arguments ] - print " public %s(%s) {" % (java_class_name(m.name), ", ".join(argList)) + print(" public %s(%s) {" % (java_class_name(m.name), ", ".join(argList))) - fieldsToNullCheckInCons = nullCheckedFields(spec, m) + fieldsToNullCheckInCons = [f for f in nullCheckedFields(spec, m)] + fieldsToNullCheckInCons.sort() for f in fieldsToNullCheckInCons: - print " if (%s == null)" % (f) - print " throw new IllegalStateException(\"Invalid configuration: '%s' must be non-null.\");" % (f) + print(" if (%s == null)" % (f)) + print(" throw new IllegalStateException(\"Invalid configuration: '%s' must be non-null.\");" % (f)) for a in m.arguments: (jfType, jfName) = (java_field_type(spec, a.domain), java_field_name(a.name)) if jfType == "Map": - print " this.%s = %s==null ? null : Collections.unmodifiableMap(new HashMap(%s));" % (jfName, jfName, jfName) + print(" this.%s = %s==null ? null : Collections.unmodifiableMap(new HashMap(%s));" % (jfName, jfName, jfName)) else: - print " this.%s = %s;" % (jfName, jfName) + print(" this.%s = %s;" % (jfName, jfName)) - print " }" + print(" }") consArgs = [ "rdr.read%s()" % (java_class_name(spec.resolveDomain(a.domain))) for a in m.arguments ] - print " public %s(MethodArgumentReader rdr) throws IOException {" % (java_class_name(m.name)) - print " this(%s);" % (", ".join(consArgs)) - print " }" + print(" public %s(MethodArgumentReader rdr) throws IOException {" % (java_class_name(m.name))) + print(" this(%s);" % (", ".join(consArgs))) + print(" }") def others(): - print - print " public int protocolClassId() { return %s; }" % (c.index) - print " public int protocolMethodId() { return %s; }" % (m.index) - print " public String protocolMethodName() { return \"%s.%s\";}" % (c.name, m.name) - print - print " public boolean hasContent() { return %s; }" % (trueOrFalse(m.hasContent)) - print - print " public Object visit(MethodVisitor visitor) throws IOException" - print " { return visitor.visit(this); }" + print() + print(" public int protocolClassId() { return %s; }" % (c.index)) + print(" public int protocolMethodId() { return %s; }" % (m.index)) + print(" public String protocolMethodName() { return \"%s.%s\";}" % (c.name, m.name)) + print() + print(" public boolean hasContent() { return %s; }" % (trueOrFalse(m.hasContent))) + print() + print(" public Object visit(MethodVisitor visitor) throws IOException") + print(" { return visitor.visit(this); }") def trueOrFalse(truthVal): if truthVal: @@ -474,103 +521,105 @@ def argument_debug_string(): appendList = [ "%s=\")\n .append(this.%s)\n .append(\"" % (a.name, java_field_name(a.name)) for a in m.arguments ] - print - print " public void appendArgumentDebugStringTo(StringBuilder acc) {" - print " acc.append(\"(%s)\");" % ", ".join(appendList) - print " }" + print() + print(" public void appendArgumentDebugStringTo(StringBuilder acc) {") + print(" acc.append(\"(%s)\");" % ", ".join(appendList)) + print(" }") def write_arguments(): - print - print " public void writeArgumentsTo(MethodArgumentWriter writer)" - print " throws IOException" - print " {" + print() + print(" public void writeArgumentsTo(MethodArgumentWriter writer)") + print(" throws IOException") + print(" {") for a in m.arguments: - print " writer.write%s(this.%s);" % (java_class_name(spec.resolveDomain(a.domain)), java_field_name(a.name)) - print " }" + print(" writer.write%s(this.%s);" % (java_class_name(spec.resolveDomain(a.domain)), java_field_name(a.name))) + print(" }") #start - print - print " public static class %s" % (java_class_name(m.name),) - print " extends Method" - print " implements com.rabbitmq.client.AMQP.%s.%s" % (java_class_name(c.name), java_class_name(m.name)) - print " {" - print " public static final int INDEX = %s;" % (m.index) - print + print() + print(" public static class %s" % (java_class_name(m.name),)) + print(" extends Method") + print(" implements com.rabbitmq.client.AMQP.%s.%s" % (java_class_name(c.name), java_class_name(m.name))) + print(" {") + print(" public static final int INDEX = %s;" % (m.index)) + print() for a in m.arguments: - print " private final %s %s;" % (java_field_type(spec, a.domain), java_field_name(a.name)) + print(" private final %s %s;" % (java_field_type(spec, a.domain), java_field_name(a.name))) getters() constructors() others() + if m.arguments: + equalsHashCode(spec, m.arguments, java_class_name(m.name), '', True) argument_debug_string() write_arguments() - print " }" - print " }" + print(" }") + print(" }") def printMethodVisitor(): - print - print " public interface MethodVisitor {" + print() + print(" public interface MethodVisitor {") for c in spec.allClasses(): for m in c.allMethods(): - print " Object visit(%s.%s x) throws IOException;" % (java_class_name(c.name), java_class_name(m.name)) - print " }" + print(" Object visit(%s.%s x) throws IOException;" % (java_class_name(c.name), java_class_name(m.name))) + print(" }") #default method visitor - print - print " public static class DefaultMethodVisitor implements MethodVisitor {" + print() + print(" public static class DefaultMethodVisitor implements MethodVisitor {") for c in spec.allClasses(): for m in c.allMethods(): - print " public Object visit(%s.%s x) throws IOException { throw new UnexpectedMethodError(x); }" % (java_class_name(c.name), java_class_name(m.name)) - print " }" + print(" public Object visit(%s.%s x) throws IOException { throw new UnexpectedMethodError(x); }" % (java_class_name(c.name), java_class_name(m.name))) + print(" }") def printMethodArgumentReader(): - print - print " public static Method readMethodFrom(DataInputStream in) throws IOException {" - print " int classId = in.readShort();" - print " int methodId = in.readShort();" - print " switch (classId) {" + print() + print(" public static Method readMethodFrom(DataInputStream in) throws IOException {") + print(" int classId = in.readShort();") + print(" int methodId = in.readShort();") + print(" switch (classId) {") for c in spec.allClasses(): - print " case %s:" % (c.index) - print " switch (methodId) {" + print(" case %s:" % (c.index)) + print(" switch (methodId) {") for m in c.allMethods(): fq_name = java_class_name(c.name) + '.' + java_class_name(m.name) - print " case %s: {" % (m.index) - print " return new %s(new MethodArgumentReader(new ValueReader(in)));" % (fq_name) - print " }" - print " default: break;" - print " } break;" - print " }" - print - print " throw new UnknownClassOrMethodId(classId, methodId);" - print " }" + print(" case %s: {" % (m.index)) + print(" return new %s(new MethodArgumentReader(new ValueReader(in)));" % (fq_name)) + print(" }") + print(" default: break;") + print(" } break;") + print(" }") + print() + print(" throw new UnknownClassOrMethodId(classId, methodId);") + print(" }") def printContentHeaderReader(): - print - print " public static AMQContentHeader readContentHeaderFrom(DataInputStream in) throws IOException {" - print " int classId = in.readShort();" - print " switch (classId) {" + print() + print(" public static AMQContentHeader readContentHeaderFrom(DataInputStream in) throws IOException {") + print(" int classId = in.readShort();") + print(" switch (classId) {") for c in spec.allClasses(): if c.fields: - print " case %s: return new %sProperties(in);" %(c.index, (java_class_name(c.name))) - print " default: break;" - print " }" - print - print " throw new UnknownClassOrMethodId(classId);" - print " }" + print(" case %s: return new %sProperties(in);" %(c.index, (java_class_name(c.name)))) + print(" default: break;") + print(" }") + print() + print(" throw new UnknownClassOrMethodId(classId);") + print(" }") printHeader() - print - print "public class AMQImpl implements AMQP {" + print() + print("public class AMQImpl implements AMQP {") for c in spec.allClasses(): printClassMethods(spec,c) - + printMethodVisitor() printMethodArgumentReader() printContentHeaderReader() - print "}" + print("}") #-------------------------------------------------------------------------------- diff --git a/config.properties b/config.properties deleted file mode 100644 index c9851fb271..0000000000 --- a/config.properties +++ /dev/null @@ -1,7 +0,0 @@ -broker.hostname=localhost -broker.port=5672 -test.main=com.rabbitmq.examples.TestMain -test.producer.rate-limit=100000 -test.producer.message-count=30000 -test.producer.commit-every=-1 -test.producer.send-completion=false diff --git a/deploy-javadoc.sh b/deploy-javadoc.sh new file mode 100755 index 0000000000..43c1ac4959 --- /dev/null +++ b/deploy-javadoc.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +DEPLOY_DIRECTORY=api/current +TAG=$(git describe --exact-match --tags $(git log -n1 --pretty='%h')) + +make deps +./mvnw -q clean javadoc:javadoc -Dmaven.javadoc.failOnError=false + +if [ -e target/javadoc-bundle-options/element-list ] + then cp target/javadoc-bundle-options/element-list target/reports/apidocs/package-list +fi + +git co gh-pages +rm -rf $DEPLOY_DIRECTORY/* +cp -r target/reports/apidocs/* $DEPLOY_DIRECTORY +git add $DEPLOY_DIRECTORY +git commit -m "Add Javadoc for $TAG" +git push origin gh-pages + + diff --git a/doc/channels/whiteboard.JPG b/doc/channels/whiteboard.JPG index 7c45bcdf71..eed267a5c1 100644 Binary files a/doc/channels/whiteboard.JPG and b/doc/channels/whiteboard.JPG differ diff --git a/doc/channels/worktransition.graffle b/doc/channels/worktransition.graffle index a8c2d6ce8e..a1feddfa7f 100644 --- a/doc/channels/worktransition.graffle +++ b/doc/channels/worktransition.graffle @@ -1,5 +1,5 @@ - + ActiveLayerIndex diff --git a/generate-observation-documentation.sh b/generate-observation-documentation.sh new file mode 100755 index 0000000000..c90b14303b --- /dev/null +++ b/generate-observation-documentation.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +./mvnw -q test-compile exec:java \ + -Dexec.mainClass=io.micrometer.docs.DocsGeneratorCommand \ + -Dexec.classpathScope="test" \ + -Dexec.args='src/main/java/com/rabbitmq/client/observation/micrometer .* target/micrometer-observation-docs' \ No newline at end of file diff --git a/lib/commons-cli-1.1.jar b/lib/commons-cli-1.1.jar deleted file mode 100644 index e633afbe68..0000000000 Binary files a/lib/commons-cli-1.1.jar and /dev/null differ diff --git a/lib/commons-io-1.2.jar b/lib/commons-io-1.2.jar deleted file mode 100644 index b2867cdde4..0000000000 Binary files a/lib/commons-io-1.2.jar and /dev/null differ diff --git a/lib/junit.jar b/lib/junit.jar deleted file mode 100644 index 674d71e89e..0000000000 Binary files a/lib/junit.jar and /dev/null differ diff --git a/mvnw b/mvnw new file mode 100755 index 0000000000..8d937f4c14 --- /dev/null +++ b/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000000..f80fbad3e7 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/nexus-upload.sh b/nexus-upload.sh deleted file mode 100755 index 51bfdaf9ab..0000000000 --- a/nexus-upload.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -set -e - -# Required flags: -# CREDS -- basic auth credentials, in form username:password -# VERSION -- the version of the bundle -# SIGNING_KEY -- the signing key to use -# GNUPG_PATH -- the path to the home directory for gnupg - -NEXUS_ROOT="https://$CREDS@oss.sonatype.org/service/local/staging/deploy/maven2/com/rabbitmq/amqp-client/$VERSION" -unset http_proxy -unset https_proxy -unset no_proxy - -for artifact in $@; do - echo "Uploading $artifact" - - rm -f $artifact.asc - gpg --homedir $GNUPG_PATH/.gnupg --local-user $SIGNING_KEY --no-tty --armor --detach-sign --output $artifact.asc $artifact - for ext in '' .asc ; do - curl --upload-file $artifact$ext $NEXUS_ROOT/$artifact$ext - for sum in md5 sha1 ; do - ${sum}sum $artifact$ext | (read a rest ; echo -n "$a") >$artifact$ext.$sum - curl --upload-file $artifact$ext.$sum $NEXUS_ROOT/$artifact$ext.$sum - done - done -done diff --git a/pom.xml b/pom.xml index 07d328aa98..38acc89baf 100644 --- a/pom.xml +++ b/pom.xml @@ -1,77 +1,920 @@ - - 4.0.0 - com.rabbitmq - amqp-client - VERSION - jar - RabbitMQ Java Client - RabbitMQ AMQP Java Client - http://www.rabbitmq.com - - - - ASL 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html - repo - - - GPL v2 - http://www.gnu.org/licenses/gpl-2.0.txt - repo - - - MPL 1.1 - http://www.mozilla.org/MPL/MPL-1.1.txt - repo - - - - - https://github.com/rabbitmq/rabbitmq-java-client.git - scm:git:https://github.com/rabbitmq/rabbitmq-java-client.git - - - - - rabbitmq.team - The RabbitMQ Team - - Developer - - - + + 4.0.0 - + com.rabbitmq + amqp-client + 6.0.0-SNAPSHOT + jar + + RabbitMQ Java Client + The RabbitMQ Java client library allows Java applications to interface with RabbitMQ. + https://www.rabbitmq.com + + + + AL 2.0 + https://www.apache.org/licenses/LICENSE-2.0.html + repo + + + GPL v2 + https://www.gnu.org/licenses/gpl-2.0.txt + repo + + + MPL 2.0 + https://www.mozilla.org/en-US/MPL/2.0/ + repo + + + + + + info@rabbitmq.com + Team RabbitMQ + Broadcom Inc. and its subsidiaries + https://rabbitmq.com + + + + + https://github.com/rabbitmq/rabbitmq-java-client + scm:git:git://github.com/rabbitmq/rabbitmq-java-client.git + scm:git:https://github.com/rabbitmq/rabbitmq-java-client.git + HEAD + + + + Broadcom Inc. and its subsidiaries + https://www.rabbitmq.com + + + + UTF-8 + UTF-8 + + true + 1.7.36 + 4.2.32 + 1.15.0 + 1.51.0 + 2.19.0 + 1.2.13 + 5.13.1 + 5.18.0 + 3.27.3 + 1.5.0 + 1.0.4 + 9.4.57.v20241219 + 1.81 + 0.10 + 2.13.1 + + 3.11.2 + 3.1.1 + 2.18.0 + 3.3.1 + 3.3.1 + 2.1.1 + 2.4.21 + 3.6.1 + 3.14.0 + 3.5.3 + 3.8.1 + 3.5.3 + 3.2.7 + 3.4.2 + 5.1.9 + 0.0.6 + 1.7.0 + 1.11 + 1.4 + 2.44.5 + 1.19.2 + + ${basedir}/src/main/scripts + + + ${basedir}/deps + ${deps.dir}/rabbitmq_codegen + 0.9.1 + + + make + ${deps.dir}/rabbit + ${rabbitmq.dir}/scripts/rabbitmqctl + + rabbit@localhost + 5672 + rabbit@node1 + 5673 + + + 6026DFCA + + + + + + in-umbrella + + + ../../UMBRELLA.md + + + + ${basedir}/.. + + + + + + use-gmake + + FreeBSD + + + gmake + + + + + + use-rabbitmqctl.bat + + Windows + + + ${rabbitmq.dir}/scripts/rabbitmqctl.bat + + + + + + disable-java8-doclint + + [1.8,) + + + -Xdoclint:none + + + + + + integration-tests + + + !skipTests + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven.failsafe.plugin.version} + + + true + ${make.bin} + ${rabbitmq.dir} + ${rabbitmqctl.bin} + + ${test-broker.A.nodename} + ${test-broker.A.node_port} + ${test-broker.B.nodename} + ${test-broker.B.node_port} + + + ${deps.dir} + + true + + **/ClientTestSuite.* + **/FunctionalTestSuite.* + **/SslTestSuite.* + **/ServerTestSuite.* + **/HaTestSuite.* + + ${test-arguments} + + + + integration-test + + integration-test + + + + verify + + verify + + + + + + + + + + + use-nio + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven.failsafe.plugin.version} + + + true + true + + ${test-arguments} + + + + + + + + + snapshots + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven.javadoc.plugin.version} + + ${javadoc.opts} + ${javadoc.joption} + true + 8 + + + + + jar + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven.gpg.plugin.version} + + + sign-artifacts + package + + sign + + + ${gpg.keyname} + + + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + - - commons-cli - commons-cli - 1.1 - test - - - - commons-io - commons-io - 1.2 - test - - - - junit - junit - 3.8.2 - test - + + + release + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + ossrh + https://oss.sonatype.org/ + false + 20 + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven.javadoc.plugin.version} + + ${javadoc.opts} + ${javadoc.joption} + true + 8 + + + + + jar + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven.gpg.plugin.version} + + + sign-artifacts + package + + sign + + + ${gpg.keyname} + + + + + + + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + milestone + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven.javadoc.plugin.version} + + ${javadoc.opts} + ${javadoc.joption} + true + 8 + + + + + jar + + + + + + + net.nicoulaj.maven.plugins + checksum-maven-plugin + ${checksum.maven.plugin.version} + + + sign-artifacts + package + + files + + + + + ${project.build.directory} + + *.jar + *.pom + + + + + MD5 + SHA-1 + + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven.gpg.plugin.version} + + + sign-artifacts + package + + sign + + + ${gpg.keyname} + + + + + + + + + packagecloud-rabbitmq-maven-milestones + packagecloud+https://packagecloud.io/rabbitmq/maven-milestones + + + + + mockito-4-on-java-8 + + 1.8 + + + 4.11.0 + + + + jvm-test-arguments-below-java-21 + + [11,21) + + + -Xshare:off + + + + jvm-test-arguments-java-21-and-more + + [21,) + + + -Xshare:off -javaagent:${org.mockito:mockito-core:jar} + + + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + io.dropwizard.metrics + metrics-core + ${metrics.version} + true + + + io.micrometer + micrometer-core + ${micrometer.version} + true + + + io.opentelemetry + opentelemetry-api + ${opentelemetry.version} + true + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + true + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.platform + junit-platform-suite + test + + + + org.junit.jupiter + junit-jupiter-params + test + + + ch.qos.logback + logback-classic + ${logback.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + org.eclipse.jetty + jetty-servlet + ${jetty.version} + test + + + org.bouncycastle + bcpkix-jdk18on + ${bouncycastle.version} + test + + + com.github.netcrusherorg + netcrusher-core + ${netcrusher.version} + test + + + io.opentelemetry + opentelemetry-sdk-testing + ${opentelemetry.version} + test + + + com.google.code.gson + gson + ${gson.version} + test + + + io.micrometer + micrometer-tracing-integration-test + ${micrometer-tracing-test.version} + test + true + + + io.opentelemetry + * + + + + + io.micrometer + micrometer-docs-generator + ${micrometer-docs-generator.version} + test + true + + + + + + + + org.junit + junit-bom + ${junit.jupiter.version} + pom + import + + + + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 5.1.0.4751 + + + + + + + + ${basedir}/src/main/resources + true + + + + + + + ${basedir}/src/test/resources + true + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven.resources.plugin.version} + + + p12 + jks + + + + + org.apache.maven.plugins + maven-dependency-plugin + ${maven-dependency-plugin.version} + + + + properties + + + + + + org.codehaus.gmaven + groovy-maven-plugin + ${groovy.maven.plugin.version} + + + org.codehaus.groovy + groovy-all + ${groovy.all.version} + + + + + + generate-sources + generate-amqp-sources + + execute + + + + + + ${codegen.dir}/amqp-rabbitmq-${codegen.spec_version}.json + +
+ ${project.build.directory}/generated-sources/src/main/java/com/rabbitmq/client/AMQP.java +
+ + ${project.build.directory}/generated-sources/src/main/java/com/rabbitmq/client/impl/AMQImpl.java + +
+ + ${groovy-scripts.dir}/generate_amqp_sources.groovy + +
+
+
+
+ + + org.codehaus.mojo + build-helper-maven-plugin + ${build.helper.maven-plugin.version} + + + add-generated-sources-dir + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/src/main/java + + + + + + + + maven-compiler-plugin + ${maven.compiler.plugin.version} + + 1.8 + 1.8 + + -Xlint:deprecation + -Xlint:unchecked + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.plugin.version} + + true + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven.jar.plugin.version} + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + com.rabbitmq.client + + + + + + + org.apache.felix + maven-bundle-plugin + ${maven.bundle.plugin.version} + + + bundle-manifest + process-classes + + manifest + + + true + + com.rabbitmq* + com.rabbitmq.client + AMQP + 0.9.1 + AMQP Working Group (www.amqp.org) + ${project.name} + ${project.version} + ${project.organization.name} + ${project.url} + + + + + + + + org.codehaus.mojo + versions-maven-plugin + ${versions.maven.plugin.version} + + + + org.apache.maven.plugins + maven-release-plugin + ${maven.release.plugin.version} + + v@{project.version} + + + + + org.apache.maven.plugins + maven-source-plugin + ${maven.source.plugin.version} + + + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven.javadoc.plugin.version} + + ${javadoc.opts} + ${javadoc.joption} + true + 8 + + + + + com.github.johnpoth + jshell-maven-plugin + ${jshell-maven-plugin.version} + + true + + + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless.version} + + + + src/main/java/com/rabbitmq/client/observation/**/*.java + src/test/java/com/rabbitmq/client/test/functional/MicrometerObservationCollectorMetrics.java + + + ${google-java-format.version} + + + + + + // Copyright (c) $YEAR Broadcom. All Rights Reserved. + // The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + // + // This software, the RabbitMQ Java client library, is triple-licensed under the + // Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 + // ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see + // LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, + // please see LICENSE-APACHE2. + // + // This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, + // either express or implied. See the LICENSE file for specific language governing + // rights and limitations of this software. + // + // If you have any questions regarding licensing, please contact us at + // info@rabbitmq.com. + + + + + +
+ + + io.packagecloud.maven.wagon + maven-packagecloud-wagon + ${maven.packagecloud.wagon.version} + + +
- - - sonatype-nexus-staging - Nexus Release Repository - http://oss.sonatype.org/service/local/staging/deploy/maven2/ - -
diff --git a/release-versions.txt b/release-versions.txt new file mode 100644 index 0000000000..8a9d479d3a --- /dev/null +++ b/release-versions.txt @@ -0,0 +1,3 @@ +RELEASE_VERSION="6.0.0.M2" +DEVELOPMENT_VERSION="6.0.0-SNAPSHOT" +RELEASE_BRANCH="main" diff --git a/scripts/runjava.bat b/scripts/runjava.bat deleted file mode 100644 index a4a3a919db..0000000000 --- a/scripts/runjava.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off - -setlocal EnableDelayedExpansion - -set CP= -for %%F in ("%~dp0"/*.jar) do set CP=!CP!;"%%F" - -java -cp %CP% %1 %2 %3 %4 %5 %6 %7 %8 %9 - -endlocal diff --git a/scripts/runjava.sh b/scripts/runjava.sh deleted file mode 100755 index 74c6aa6ac6..0000000000 --- a/scripts/runjava.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -RABBIT_JARS= -for d in `dirname $0`/*.jar -do - RABBIT_JARS="$d:$RABBIT_JARS" -done -exec java -cp "$RABBIT_JARS" "$@" diff --git a/scripts/runperftest.sh b/scripts/runperftest.sh deleted file mode 100644 index 2308749fb9..0000000000 --- a/scripts/runperftest.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -run() { - echo "=== running with '$2'" - sh `dirname $0`/runjava.sh com.rabbitmq.examples.PerfTest -h $1 -z 10 -i 20 $2 - sleep 2 -} - -for sz in "" "-s 1000"; do - for pers in "" "-f persistent"; do - for args in \ - "" \ - "-a" \ - "-m 1" \ - "-m 1 -n 1" \ - "-m 10" \ - "-m 10 -n 10" \ - ; do - run $1 "${args} ${pers} ${sz}" - done - done -done - -for args in "-a -f mandatory" "-a -f mandatory -f immediate"; do - run $1 "$args" -done diff --git a/scripts/stresspersister.sh b/scripts/stresspersister.sh deleted file mode 100644 index 6adc349a22..0000000000 --- a/scripts/stresspersister.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/sh - -commentText=$1 -shift - -if [ -z "$commentText" ]; then - echo "Comment text must be supplied!" - exit 1 -fi - -echo "Comment text: $commentText. Press enter to continue." -read dummy - -function run1 { - (while true; do (date +%s.%N; ps ax -o '%mem rss sz vsz args' | grep "beam.*-s rabbit" | grep -v grep) | tr '\n' ' ' | awk '{print $1,$2/100,$3,$4,$5}'; sleep 1; done) > memlog.txt & - memlogger=$! - echo "STARTED MEMLOGGER $memlogger" - sleep 2 - sh ./runjava.sh com.rabbitmq.examples.StressPersister -B $1 -b $2 -C $commentText | tee stressoutput.txt - logfile=$(head -1 stressoutput.txt) - sleep 2 - kill $memlogger - echo "STOPPED MEMLOGGER $memlogger" - baselog=$(basename $logfile .out) - mv memlog.txt $baselog.mem - grep -v '^#' $logfile > stressoutput.txt - mv stressoutput.txt $logfile -} - -function run32b { - run1 32b 5000 - run1 32b 10000 - run1 32b 20000 - run1 32b 40000 - run1 32b 80000 -} - -function run1m { - run1 1m 125 - run1 1m 250 - run1 1m 500 - run1 1m 1000 - run1 1m 2000 - run1 1m 4000 -} - -function chartall { - for logfile in *.out - do - echo $logfile - baselog=$(basename $logfile .out) - firsttimestamp=$(cat $baselog.mem | head -1 | awk '{print $1}') - cat > $baselog.gnuplot < $baselog.png - done -} - -run32b -run1m -chartall diff --git a/src/com/rabbitmq/client/Address.java b/src/com/rabbitmq/client/Address.java deleted file mode 100644 index 40df18801d..0000000000 --- a/src/com/rabbitmq/client/Address.java +++ /dev/null @@ -1,109 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -/** - * A representation of network addresses, i.e. host/port pairs, - * with some utility functions for parsing address strings. -*/ -public class Address { - /** host name **/ - private final String _host; - /** port number **/ - private final int _port; - - /** - * Construct an address from a host name and port number. - * @param host the host name - * @param port the port number - */ - public Address(String host, int port) { - _host = host; - _port = port; - } - - /** - * Construct an address from a host. - * @param host the host name - */ - public Address(String host) { - _host = host; - _port = ConnectionFactory.USE_DEFAULT_PORT; - } - - /** - * Get the host name - * @return the host name - */ - public String getHost() { - return _host; - } - - /** - * Get the port number - * @return the port number - */ - public int getPort() { - return _port; - } - - /** - * Factory method: takes a formatted addressString string as construction parameter - * @param addressString an addressString of the form "host[:port]". - * @return an {@link Address} from the given data - */ - public static Address parseAddress(String addressString) { - int idx = addressString.indexOf(':'); - return (idx == -1) ? - new Address(addressString) : - new Address(addressString.substring(0, idx), - Integer.parseInt(addressString.substring(idx+1))); - } - - /** - * Array-based factory method: takes an array of formatted address strings as construction parameter - * @param addresses array of strings of form "host[:port],..." - * @return a list of {@link Address} values - */ - public static Address[] parseAddresses(String addresses) { - String[] addrs = addresses.split(" *, *"); - Address[] res = new Address[addrs.length]; - for (int i = 0; i < addrs.length; i++) { - res[i] = Address.parseAddress(addrs[i]); - } - return res; - } - - @Override public int hashCode() { - return 31 * _host.hashCode() + _port; - } - - @Override public boolean equals(Object obj) { - if(this == obj) - return true; - if (obj == null || getClass() != obj.getClass()) - return false; - final Address addr = (Address)obj; - return _host.equals(addr._host) && _port == addr._port; - } - - @Override public String toString() { - return _port == -1 ? _host : _host + ":" + _port; - } - -} diff --git a/src/com/rabbitmq/client/AuthenticationFailureException.java b/src/com/rabbitmq/client/AuthenticationFailureException.java deleted file mode 100644 index e2dbe64de0..0000000000 --- a/src/com/rabbitmq/client/AuthenticationFailureException.java +++ /dev/null @@ -1,28 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2013-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client; - -/** - * Thrown when the broker refuses access due to an authentication failure. - */ - -public class AuthenticationFailureException extends PossibleAuthenticationFailureException -{ - public AuthenticationFailureException(String reason) { - super(reason); - } -} diff --git a/src/com/rabbitmq/client/BlockedListener.java b/src/com/rabbitmq/client/BlockedListener.java deleted file mode 100644 index bf7728c3d2..0000000000 --- a/src/com/rabbitmq/client/BlockedListener.java +++ /dev/null @@ -1,29 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import java.io.IOException; - -/** - * Implement this interface in order to be notified of connection block and - * unblock events. - */ -public interface BlockedListener { - void handleBlocked(String reason) throws IOException; - void handleUnblocked() throws IOException; -} diff --git a/src/com/rabbitmq/client/ConfirmListener.java b/src/com/rabbitmq/client/ConfirmListener.java deleted file mode 100644 index 7109de0b76..0000000000 --- a/src/com/rabbitmq/client/ConfirmListener.java +++ /dev/null @@ -1,35 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import java.io.IOException; - -/** - * Implement this interface in order to be notified of Confirm events. - * Acks represent messages handled successfully; Nacks represent - * messages lost by the broker. Note, the lost messages could still - * have been delivered to consumers, but the broker cannot guarantee - * this. - */ -public interface ConfirmListener { - void handleAck(long deliveryTag, boolean multiple) - throws IOException; - - void handleNack(long deliveryTag, boolean multiple) - throws IOException; -} diff --git a/src/com/rabbitmq/client/ConnectionFactory.java b/src/com/rabbitmq/client/ConnectionFactory.java deleted file mode 100644 index c80b33d0e9..0000000000 --- a/src/com/rabbitmq/client/ConnectionFactory.java +++ /dev/null @@ -1,673 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client; - -import java.io.IOException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.util.Map; -import java.util.concurrent.ExecutorService; - -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLDecoder; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; - -import javax.net.SocketFactory; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; - -import com.rabbitmq.client.impl.AMQConnection; -import com.rabbitmq.client.impl.ConnectionParams; -import com.rabbitmq.client.impl.DefaultExceptionHandler; -import com.rabbitmq.client.impl.FrameHandler; -import com.rabbitmq.client.impl.FrameHandlerFactory; -import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; - -/** - * Convenience "factory" class to facilitate opening a {@link Connection} to an AMQP broker. - */ - -public class ConnectionFactory implements Cloneable { - - /** Default user name */ - public static final String DEFAULT_USER = "guest"; - /** Default password */ - public static final String DEFAULT_PASS = "guest"; - /** Default virtual host */ - public static final String DEFAULT_VHOST = "/"; - /** Default maximum channel number; - * zero for unlimited */ - public static final int DEFAULT_CHANNEL_MAX = 0; - /** Default maximum frame size; - * zero means no limit */ - public static final int DEFAULT_FRAME_MAX = 0; - /** Default heart-beat interval; - * zero means no heart-beats */ - public static final int DEFAULT_HEARTBEAT = 0; - /** The default host */ - public static final String DEFAULT_HOST = "localhost"; - /** 'Use the default port' port */ - public static final int USE_DEFAULT_PORT = -1; - /** The default non-ssl port */ - public static final int DEFAULT_AMQP_PORT = AMQP.PROTOCOL.PORT; - /** The default ssl port */ - public static final int DEFAULT_AMQP_OVER_SSL_PORT = 5671; - /** The default connection timeout; - * zero means wait indefinitely */ - public static final int DEFAULT_CONNECTION_TIMEOUT = 0; - /** The default shutdown timeout; - * zero means wait indefinitely */ - public static final int DEFAULT_SHUTDOWN_TIMEOUT = 10000; - - /** The default SSL protocol */ - private static final String DEFAULT_SSL_PROTOCOL = "TLSv1"; - - private String username = DEFAULT_USER; - private String password = DEFAULT_PASS; - private String virtualHost = DEFAULT_VHOST; - private String host = DEFAULT_HOST; - private int port = USE_DEFAULT_PORT; - private int requestedChannelMax = DEFAULT_CHANNEL_MAX; - private int requestedFrameMax = DEFAULT_FRAME_MAX; - private int requestedHeartbeat = DEFAULT_HEARTBEAT; - private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; - private int shutdownTimeout = DEFAULT_SHUTDOWN_TIMEOUT; - private Map _clientProperties = AMQConnection.defaultClientProperties(); - private SocketFactory factory = SocketFactory.getDefault(); - private SaslConfig saslConfig = DefaultSaslConfig.PLAIN; - private ExecutorService sharedExecutor; - private ThreadFactory threadFactory = Executors.defaultThreadFactory(); - private SocketConfigurator socketConf = new DefaultSocketConfigurator(); - private ExceptionHandler exceptionHandler = new DefaultExceptionHandler(); - - private boolean automaticRecovery = false; - private boolean topologyRecovery = true; - - // long is used to make sure the users can use both ints - // and longs safely. It is unlikely that anybody'd need - // to use recovery intervals > Integer.MAX_VALUE in practice. - private long networkRecoveryInterval = 5000; - - /** @return the default host to use for connections */ - public String getHost() { - return host; - } - - /** @param host the default host to use for connections */ - public void setHost(String host) { - this.host = host; - } - - public static int portOrDefault(int port, boolean ssl) { - if (port != USE_DEFAULT_PORT) return port; - else if (ssl) return DEFAULT_AMQP_OVER_SSL_PORT; - else return DEFAULT_AMQP_PORT; - } - - /** @return the default port to use for connections */ - public int getPort() { - return portOrDefault(port, isSSL()); - } - - /** - * Set the target port. - * @param port the default port to use for connections - */ - public void setPort(int port) { - this.port = port; - } - - /** - * Retrieve the user name. - * @return the AMQP user name to use when connecting to the broker - */ - public String getUsername() { - return this.username; - } - - /** - * Set the user name. - * @param username the AMQP user name to use when connecting to the broker - */ - public void setUsername(String username) { - this.username = username; - } - - /** - * Retrieve the password. - * @return the password to use when connecting to the broker - */ - public String getPassword() { - return this.password; - } - - /** - * Set the password. - * @param password the password to use when connecting to the broker - */ - public void setPassword(String password) { - this.password = password; - } - - /** - * Retrieve the virtual host. - * @return the virtual host to use when connecting to the broker - */ - public String getVirtualHost() { - return this.virtualHost; - } - - /** - * Set the virtual host. - * @param virtualHost the virtual host to use when connecting to the broker - */ - public void setVirtualHost(String virtualHost) { - this.virtualHost = virtualHost; - } - - - /** - * Convenience method for setting the fields in an AMQP URI: host, - * port, username, password and virtual host. If any part of the - * URI is ommited, the ConnectionFactory's corresponding variable - * is left unchanged. - * @param uri is the AMQP URI containing the data - */ - public void setUri(URI uri) - throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException - { - if ("amqp".equals(uri.getScheme().toLowerCase())) { - // nothing special to do - } else if ("amqps".equals(uri.getScheme().toLowerCase())) { - setPort(DEFAULT_AMQP_OVER_SSL_PORT); - useSslProtocol(); - } else { - throw new IllegalArgumentException("Wrong scheme in AMQP URI: " + - uri.getScheme()); - } - - String host = uri.getHost(); - if (host != null) { - setHost(host); - } - - int port = uri.getPort(); - if (port != -1) { - setPort(port); - } - - String userInfo = uri.getRawUserInfo(); - if (userInfo != null) { - String userPass[] = userInfo.split(":"); - if (userPass.length > 2) { - throw new IllegalArgumentException("Bad user info in AMQP " + - "URI: " + userInfo); - } - - setUsername(uriDecode(userPass[0])); - if (userPass.length == 2) { - setPassword(uriDecode(userPass[1])); - } - } - - String path = uri.getRawPath(); - if (path != null && path.length() > 0) { - if (path.indexOf('/', 1) != -1) { - throw new IllegalArgumentException("Multiple segments in " + - "path of AMQP URI: " + - path); - } - - setVirtualHost(uriDecode(uri.getPath().substring(1))); - } - } - - /** - * Convenience method for setting the fields in an AMQP URI: host, - * port, username, password and virtual host. If any part of the - * URI is ommited, the ConnectionFactory's corresponding variable - * is left unchanged. Note that not all valid AMQP URIs are - * accepted; in particular, the hostname must be given if the - * port, username or password are given, and escapes in the - * hostname are not permitted. - * @param uriString is the AMQP URI containing the data - */ - public void setUri(String uriString) - throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException - { - setUri(new URI(uriString)); - } - - private String uriDecode(String s) { - try { - // URLDecode decodes '+' to a space, as for - // form encoding. So protect plus signs. - return URLDecoder.decode(s.replace("+", "%2B"), "US-ASCII"); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Retrieve the requested maximum channel number - * @return the initially requested maximum channel number; zero for unlimited - */ - public int getRequestedChannelMax() { - return this.requestedChannelMax; - } - - /** - * Set the requested maximum channel number - * @param requestedChannelMax initially requested maximum channel number; zero for unlimited - */ - public void setRequestedChannelMax(int requestedChannelMax) { - this.requestedChannelMax = requestedChannelMax; - } - - /** - * Retrieve the requested maximum frame size - * @return the initially requested maximum frame size, in octets; zero for unlimited - */ - public int getRequestedFrameMax() { - return this.requestedFrameMax; - } - - /** - * Set the requested maximum frame size - * @param requestedFrameMax initially requested maximum frame size, in octets; zero for unlimited - */ - public void setRequestedFrameMax(int requestedFrameMax) { - this.requestedFrameMax = requestedFrameMax; - } - - /** - * Retrieve the requested heartbeat interval. - * @return the initially requested heartbeat interval, in seconds; zero for none - */ - public int getRequestedHeartbeat() { - return this.requestedHeartbeat; - } - - /** - * Set the connection timeout. - * @param connectionTimeout connection establishment timeout in milliseconds; zero for infinite - */ - public void setConnectionTimeout(int connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - /** - * Retrieve the connection timeout. - * @return the connection timeout, in milliseconds; zero for infinite - */ - public int getConnectionTimeout() { - return this.connectionTimeout; - } - - /** - * Set the shutdown timeout. This is the amount of time that Consumer implementations have to - * continue working through deliveries (and other Consumer callbacks) after the connection - * has closed but before the ConsumerWorkService is torn down. If consumers exceed this timeout - * then any remaining queued deliveries (and other Consumer callbacks, including - * the Consumer's handleShutdownSignal() invocation) will be lost. - * @param shutdownTimeout shutdown timeout in milliseconds; zero for infinite; default 10000 - */ - public void setShutdownTimeout(int shutdownTimeout) { - this.shutdownTimeout = shutdownTimeout; - } - - /** - * Retrieve the shutdown timeout. - * @return the shutdown timeout, in milliseconds; zero for infinite - */ - public int getShutdownTimeout() { - return shutdownTimeout; - } - - /** - * Set the requested heartbeat timeout. Heartbeat frames will be sent at about 1/2 the timeout interval. - * @param requestedHeartbeat the initially requested heartbeat timeout, in seconds; zero for none - * @see RabbitMQ Heartbeats Guide - */ - public void setRequestedHeartbeat(int requestedHeartbeat) { - this.requestedHeartbeat = requestedHeartbeat; - } - - /** - * Retrieve the currently-configured table of client properties - * that will be sent to the server during connection - * startup. Clients may add, delete, and alter keys in this - * table. Such changes will take effect when the next new - * connection is started using this factory. - * @return the map of client properties - * @see #setClientProperties - */ - public Map getClientProperties() { - return _clientProperties; - } - - /** - * Replace the table of client properties that will be sent to the - * server during subsequent connection startups. - * @param clientProperties the map of extra client properties - * @see #getClientProperties - */ - public void setClientProperties(Map clientProperties) { - _clientProperties = clientProperties; - } - - /** - * Gets the sasl config to use when authenticating - * @return the sasl config - * @see com.rabbitmq.client.SaslConfig - */ - public SaslConfig getSaslConfig() { - return saslConfig; - } - - /** - * Sets the sasl config to use when authenticating - * @param saslConfig - * @see com.rabbitmq.client.SaslConfig - */ - public void setSaslConfig(SaslConfig saslConfig) { - this.saslConfig = saslConfig; - } - - /** - * Retrieve the socket factory used to make connections with. - */ - public SocketFactory getSocketFactory() { - return this.factory; - } - - /** - * Set the socket factory used to make connections with. Can be - * used to enable SSL connections by passing in a - * javax.net.ssl.SSLSocketFactory instance. - * - * @see #useSslProtocol - */ - public void setSocketFactory(SocketFactory factory) { - this.factory = factory; - } - - /** - * Get the socket configurator. - * - * @see #setSocketConfigurator(SocketConfigurator) - */ - @SuppressWarnings("unused") - public SocketConfigurator getSocketConfigurator() { - return socketConf; - } - - /** - * Set the socket configurator. This gets a chance to "configure" a socket - * before it has been opened. The default socket configurator disables - * Nagle's algorithm. - * - * @param socketConfigurator the configurator to use - */ - public void setSocketConfigurator(SocketConfigurator socketConfigurator) { - this.socketConf = socketConfigurator; - } - - /** - * Set the executor to use by default for newly created connections. - * All connections that use this executor share it. - * - * It's developer's responsibility to shut down the executor - * when it is no longer needed. - * - * @param executor - */ - public void setSharedExecutor(ExecutorService executor) { - this.sharedExecutor = executor; - } - - /** - * Retrieve the thread factory used to instantiate new threads. - * @see ThreadFactory - */ - public ThreadFactory getThreadFactory() { - return threadFactory; - } - - /** - * Set the thread factory used to instantiate new threads. - * @see ThreadFactory - */ - public void setThreadFactory(ThreadFactory threadFactory) { - this.threadFactory = threadFactory; - } - - /** - * Get the exception handler. - * - * @see com.rabbitmq.client.ExceptionHandler - */ - public ExceptionHandler getExceptionHandler() { - return exceptionHandler; - } - - /** - * Set the exception handler to use for newly created connections. - * @see com.rabbitmq.client.ExceptionHandler - */ - public void setExceptionHandler(ExceptionHandler exceptionHandler) { - if (exceptionHandler == null) { - throw new IllegalArgumentException("exception handler cannot be null!"); - } - this.exceptionHandler = exceptionHandler; - } - - public boolean isSSL(){ - return getSocketFactory() instanceof SSLSocketFactory; - } - - /** - * Convenience method for setting up a SSL socket factory, using - * the DEFAULT_SSL_PROTOCOL and a trusting TrustManager. - */ - public void useSslProtocol() - throws NoSuchAlgorithmException, KeyManagementException - { - useSslProtocol(DEFAULT_SSL_PROTOCOL); - } - - /** - * Convenience method for setting up a SSL socket factory, using - * the supplied protocol and a very trusting TrustManager. - */ - public void useSslProtocol(String protocol) - throws NoSuchAlgorithmException, KeyManagementException - { - useSslProtocol(protocol, new NullTrustManager()); - } - - /** - * Convenience method for setting up an SSL socket factory. - * Pass in the SSL protocol to use, e.g. "TLSv1" or "TLSv1.2". - * - * @param protocol SSL protocol to use. - */ - public void useSslProtocol(String protocol, TrustManager trustManager) - throws NoSuchAlgorithmException, KeyManagementException - { - SSLContext c = SSLContext.getInstance(protocol); - c.init(null, new TrustManager[] { trustManager }, null); - useSslProtocol(c); - } - - /** - * Convenience method for setting up an SSL socket factory. - * Pass in an initialized SSLContext. - * - * @param context An initialized SSLContext - */ - public void useSslProtocol(SSLContext context) - { - setSocketFactory(context.getSocketFactory()); - } - - /** - * Returns true if automatic connection recovery is enabled, false otherwise - * @return true if automatic connection recovery is enabled, false otherwise - */ - public boolean isAutomaticRecoveryEnabled() { - return automaticRecovery; - } - - /** - * Enables or disables automatic connection recovery - * @param automaticRecovery if true, enables connection recovery - */ - public void setAutomaticRecoveryEnabled(boolean automaticRecovery) { - this.automaticRecovery = automaticRecovery; - } - - /** - * Returns true if topology recovery is enabled, false otherwise - * @return true if topology recovery is enabled, false otherwise - */ - @SuppressWarnings("unused") - public boolean isTopologyRecoveryEnabled() { - return topologyRecovery; - } - - /** - * Enables or disables topology recovery - * @param topologyRecovery if true, enables topology recovery - */ - public void setTopologyRecoveryEnabled(boolean topologyRecovery) { - this.topologyRecovery = topologyRecovery; - } - - protected FrameHandlerFactory createFrameHandlerFactory() throws IOException { - return new FrameHandlerFactory(connectionTimeout, factory, socketConf, isSSL()); - } - - /** - * Create a new broker connection - * @param addrs an array of known broker addresses (hostname/port pairs) to try in order - * @return an interface to the connection - * @throws IOException if it encounters a problem - */ - public Connection newConnection(Address[] addrs) throws IOException { - return newConnection(this.sharedExecutor, addrs); - } - - /** - * Create a new broker connection - * @param executor thread execution service for consumers on the connection - * @param addrs an array of known broker addresses (hostname/port pairs) to try in order - * @return an interface to the connection - * @throws java.io.IOException if it encounters a problem - */ - public Connection newConnection(ExecutorService executor, Address[] addrs) - throws IOException - { - FrameHandlerFactory fhFactory = createFrameHandlerFactory(); - ConnectionParams params = params(executor); - - if (isAutomaticRecoveryEnabled()) { - // see com.rabbitmq.client.impl.recovery.RecoveryAwareAMQConnectionFactory#newConnection - AutorecoveringConnection conn = new AutorecoveringConnection(params, fhFactory, addrs); - conn.init(); - return conn; - } else { - IOException lastException = null; - for (Address addr : addrs) { - try { - FrameHandler handler = fhFactory.create(addr); - AMQConnection conn = new AMQConnection(params, handler); - conn.start(); - return conn; - } catch (IOException e) { - lastException = e; - } - } - throw (lastException != null) ? lastException : new IOException("failed to connect"); - } - } - - public ConnectionParams params(ExecutorService executor) { - return new ConnectionParams(username, password, executor, virtualHost, getClientProperties(), - requestedFrameMax, requestedChannelMax, requestedHeartbeat, shutdownTimeout, saslConfig, - networkRecoveryInterval, topologyRecovery, exceptionHandler, threadFactory); - } - - /** - * Create a new broker connection - * @return an interface to the connection - * @throws IOException if it encounters a problem - */ - public Connection newConnection() throws IOException { - return newConnection(this.sharedExecutor, - new Address[] {new Address(getHost(), getPort())} - ); - } - - /** - * Create a new broker connection - * @param executor thread execution service for consumers on the connection - * @return an interface to the connection - * @throws IOException if it encounters a problem - */ - public Connection newConnection(ExecutorService executor) throws IOException { - return newConnection(executor, - new Address[] {new Address(getHost(), getPort())} - ); - } - - @Override public ConnectionFactory clone(){ - try { - return (ConnectionFactory)super.clone(); - } catch (CloneNotSupportedException e) { - throw new Error(e); - } - } - - /** - * Returns automatic connection recovery interval in milliseconds. - * @return how long will automatic recovery wait before attempting to reconnect, in ms; default is 5000 - */ - public long getNetworkRecoveryInterval() { - return networkRecoveryInterval; - } - - /** - * Sets connection recovery interval. Default is 5000. - * @param networkRecoveryInterval how long will automatic recovery wait before attempting to reconnect, in ms - */ - public void setNetworkRecoveryInterval(int networkRecoveryInterval) { - this.networkRecoveryInterval = networkRecoveryInterval; - } - - /** - * Sets connection recovery interval. Default is 5000. - * @param networkRecoveryInterval how long will automatic recovery wait before attempting to reconnect, in ms - */ - public void setNetworkRecoveryInterval(long networkRecoveryInterval) { - this.networkRecoveryInterval = networkRecoveryInterval; - } -} diff --git a/src/com/rabbitmq/client/ConsumerCancelledException.java b/src/com/rabbitmq/client/ConsumerCancelledException.java deleted file mode 100644 index a322f73fcc..0000000000 --- a/src/com/rabbitmq/client/ConsumerCancelledException.java +++ /dev/null @@ -1,36 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client; - -import com.rabbitmq.utility.SensibleClone; - -public class ConsumerCancelledException extends RuntimeException implements - SensibleClone { - - /** Default for non-checking. */ - private static final long serialVersionUID = 1L; - - public ConsumerCancelledException sensibleClone() { - try { - return (ConsumerCancelledException) super.clone(); - } catch (CloneNotSupportedException e) { - // You've got to be kidding me - throw new Error(e); - } - } - -} diff --git a/src/com/rabbitmq/client/DefaultSocketConfigurator.java b/src/com/rabbitmq/client/DefaultSocketConfigurator.java deleted file mode 100644 index ec99b4145b..0000000000 --- a/src/com/rabbitmq/client/DefaultSocketConfigurator.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.rabbitmq.client; - -import java.io.IOException; -import java.net.Socket; - -public class DefaultSocketConfigurator implements SocketConfigurator { - /** - * Provides a hook to insert custom configuration of the sockets - * used to connect to an AMQP server before they connect. - * - * The default behaviour of this method is to disable Nagle's - * algorithm to get more consistently low latency. However it - * may be overridden freely and there is no requirement to retain - * this behaviour. - * - * @param socket The socket that is to be used for the Connection - */ - public void configure(Socket socket) throws IOException { - // disable Nagle's algorithm, for more consistently low latency - socket.setTcpNoDelay(true); - } -} diff --git a/src/com/rabbitmq/client/FlowListener.java b/src/com/rabbitmq/client/FlowListener.java deleted file mode 100644 index df09664844..0000000000 --- a/src/com/rabbitmq/client/FlowListener.java +++ /dev/null @@ -1,29 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import java.io.IOException; - -/** - * Implement this interface in order to be notified of Channel.Flow - * events. - */ -public interface FlowListener { - void handleFlow(boolean active) - throws IOException; -} diff --git a/src/com/rabbitmq/client/LongString.java b/src/com/rabbitmq/client/LongString.java deleted file mode 100644 index 28d5a24385..0000000000 --- a/src/com/rabbitmq/client/LongString.java +++ /dev/null @@ -1,56 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import java.io.DataInputStream; -import java.io.IOException; - -/** - * An object providing access to a LongString. - * This might be implemented to read directly from connection - * socket, depending on the size of the content to be read - - * long strings may contain up to 4Gb of content. - */ -public interface LongString -{ - public static final long MAX_LENGTH = 0xffffffffL; - - /** - * @return the length of the {@link LongString} in bytes >= 0 <= MAX_LENGTH - */ - public long length(); - - /** - * Get the content stream. - * Repeated calls to this function return the same stream, - * which may not support rewind. - * @return An input stream that reads the content of the {@link LongString} - * @throws IOException if an error is encountered - */ - public DataInputStream getStream() throws IOException; - - /** - * Get the content as a byte array. This need not be a copy. Updates to the - * returned array may change the value of the {@link LongString}. - * Repeated calls to this function may return the same array. - * This function will fail if this.length() > Integer.MAX_VALUE, - * throwing an {@link IllegalStateException}. - * @return the array of bytes containing the content of the {@link LongString} - */ - public byte [] getBytes(); -} diff --git a/src/com/rabbitmq/client/MalformedFrameException.java b/src/com/rabbitmq/client/MalformedFrameException.java deleted file mode 100644 index 12700140ab..0000000000 --- a/src/com/rabbitmq/client/MalformedFrameException.java +++ /dev/null @@ -1,36 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import java.io.IOException; - -/** - * Encapsulates a frame format error at the wire level. - */ -public class MalformedFrameException extends IOException { - /** Standard serialization ID. */ - private static final long serialVersionUID = 1L; - - /** - * Instantiate a MalformedFrameException. - * @param reason a string describing the exception - */ - public MalformedFrameException(String reason) { - super(reason); - } -} diff --git a/src/com/rabbitmq/client/Method.java b/src/com/rabbitmq/client/Method.java deleted file mode 100644 index dfafe9a596..0000000000 --- a/src/com/rabbitmq/client/Method.java +++ /dev/null @@ -1,42 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -/** - * Public interface to objects representing an AMQP method - see the spec for details. - */ - -public interface Method { - /** - * Retrieve the protocol class ID - * @return the AMQP protocol class ID of this Method - */ - int protocolClassId(); /* properly an unsigned short */ - - /** - * Retrieve the protocol method ID - * @return the AMQP protocol method ID of this Method - */ - int protocolMethodId(); /* properly an unsigned short */ - - /** - * Retrieve the method name - * @return the AMQP protocol method name of this Method - */ - String protocolMethodName(); -} diff --git a/src/com/rabbitmq/client/MissedHeartbeatException.java b/src/com/rabbitmq/client/MissedHeartbeatException.java deleted file mode 100644 index 34afccf9d2..0000000000 --- a/src/com/rabbitmq/client/MissedHeartbeatException.java +++ /dev/null @@ -1,33 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import java.net.SocketTimeoutException; - -/** - * Encapsulates an exception indicating that the connection has missed too many heartbeats - * and is being shut down. - */ - -public class MissedHeartbeatException extends SocketTimeoutException { - private static final long serialVersionUID = 1L; - - public MissedHeartbeatException(String reason) { - super(reason); - } -} diff --git a/src/com/rabbitmq/client/NullTrustManager.java b/src/com/rabbitmq/client/NullTrustManager.java deleted file mode 100644 index 8aef053bf3..0000000000 --- a/src/com/rabbitmq/client/NullTrustManager.java +++ /dev/null @@ -1,51 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import java.security.cert.X509Certificate; - -import javax.net.ssl.X509TrustManager; - -/** - * Convenience class providing a default implementation of javax.net.ssl.X509TrustManager. - * Trusts every single certificate presented to it. - */ -public class NullTrustManager implements X509TrustManager { - /** - * Doesn't even bother looking at its arguments, simply returns, - * which makes the check succeed. - */ - public void checkClientTrusted(X509Certificate[] chain, String authType) { - // Do nothing. - } - - /** - * Doesn't even bother looking at its arguments, simply returns, - * which makes the check succeed. - */ - public void checkServerTrusted(X509Certificate[] chain, String authType) { - // Do nothing. - } - - /** - * Always returns an empty array of X509Certificates. - */ - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } -} diff --git a/src/com/rabbitmq/client/PossibleAuthenticationFailureException.java b/src/com/rabbitmq/client/PossibleAuthenticationFailureException.java deleted file mode 100644 index 33b9dcdb69..0000000000 --- a/src/com/rabbitmq/client/PossibleAuthenticationFailureException.java +++ /dev/null @@ -1,39 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client; - -import java.io.IOException; - -/** - * Thrown when the likely cause is an authentication failure. - */ -public class PossibleAuthenticationFailureException extends IOException -{ - /** Default for non-checking. */ - private static final long serialVersionUID = 1L; - - public PossibleAuthenticationFailureException(Throwable cause) - { - super("Possibly caused by authentication failure"); - super.initCause(cause); - } - - public PossibleAuthenticationFailureException(String reason) - { - super(reason); - } -} diff --git a/src/com/rabbitmq/client/Recoverable.java b/src/com/rabbitmq/client/Recoverable.java deleted file mode 100644 index f7efe8ea5f..0000000000 --- a/src/com/rabbitmq/client/Recoverable.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.rabbitmq.client; - -/** - * Provides a way to register (network, AMQP 0-9-1) connection recovery - * callbacks. - */ -public interface Recoverable { - /** - * Registers a connection recovery callback. - * - * @param listener Callback function - */ - public void addRecoveryListener(RecoveryListener listener); - - public void removeRecoveryListener(RecoveryListener listener); -} diff --git a/src/com/rabbitmq/client/RecoveryListener.java b/src/com/rabbitmq/client/RecoveryListener.java deleted file mode 100644 index 88b0ece2e5..0000000000 --- a/src/com/rabbitmq/client/RecoveryListener.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.rabbitmq.client; - -/** - * A RecoveryListener receives notifications about completed automatic connection - * recovery. - * - * @since 3.3.0 - */ -public interface RecoveryListener { - public void handleRecovery(Recoverable recoverable); -} diff --git a/src/com/rabbitmq/client/ReturnListener.java b/src/com/rabbitmq/client/ReturnListener.java deleted file mode 100644 index b634a2c5c9..0000000000 --- a/src/com/rabbitmq/client/ReturnListener.java +++ /dev/null @@ -1,36 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import java.io.IOException; - -/** - * Implement this interface in order to be notified of failed - * deliveries when basicPublish is called with "mandatory" or - * "immediate" flags set. - * @see Channel#basicPublish - */ -public interface ReturnListener { - void handleReturn(int replyCode, - String replyText, - String exchange, - String routingKey, - AMQP.BasicProperties properties, - byte[] body) - throws IOException; -} diff --git a/src/com/rabbitmq/client/RpcClient.java b/src/com/rabbitmq/client/RpcClient.java deleted file mode 100644 index 48c944954d..0000000000 --- a/src/com/rabbitmq/client/RpcClient.java +++ /dev/null @@ -1,329 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.TimeoutException; - -import com.rabbitmq.client.impl.MethodArgumentReader; -import com.rabbitmq.client.impl.MethodArgumentWriter; -import com.rabbitmq.client.impl.ValueReader; -import com.rabbitmq.client.impl.ValueWriter; -import com.rabbitmq.utility.BlockingCell; - -/** - * Convenience class which manages simple RPC-style communication. - * The class is agnostic about the format of RPC arguments / return values. - * It simply provides a mechanism for sending a message to an exchange with a given routing key, - * and waiting for a response. -*/ -public class RpcClient { - /** Channel we are communicating on */ - private final Channel _channel; - /** Exchange to send requests to */ - private final String _exchange; - /** Routing key to use for requests */ - private final String _routingKey; - /** timeout to use on call responses */ - private final int _timeout; - /** NO_TIMEOUT value must match convention on {@link BlockingCell#uninterruptibleGet(int)} */ - protected final static int NO_TIMEOUT = -1; - - /** Map from request correlation ID to continuation BlockingCell */ - private final Map> _continuationMap = new HashMap>(); - /** Contains the most recently-used request correlation ID */ - private int _correlationId; - - /** Consumer attached to our reply queue */ - private DefaultConsumer _consumer; - - /** - * Construct a new RpcClient that will communicate on the given channel, sending - * requests to the given exchange with the given routing key. - *

- * Causes the creation of a temporary private autodelete queue. - * @param channel the channel to use for communication - * @param exchange the exchange to connect to - * @param routingKey the routing key - * @param timeout milliseconds before timing out on wait for response - * @throws IOException if an error is encountered - */ - public RpcClient(Channel channel, String exchange, String routingKey, int timeout) throws IOException { - _channel = channel; - _exchange = exchange; - _routingKey = routingKey; - if (timeout < NO_TIMEOUT) throw new IllegalArgumentException("Timeout arguument must be NO_TIMEOUT(-1) or non-negative."); - _timeout = timeout; - _correlationId = 0; - - _consumer = setupConsumer(); - } - - /** - * Construct a new RpcClient that will communicate on the given channel, sending - * requests to the given exchange with the given routing key. - *

- * Causes the creation of a temporary private autodelete queue. - *

- * Waits forever for responses (that is, no timeout). - * @param channel the channel to use for communication - * @param exchange the exchange to connect to - * @param routingKey the routing key - * @throws IOException if an error is encountered - */ - public RpcClient(Channel channel, String exchange, String routingKey) throws IOException { - this(channel, exchange, routingKey, NO_TIMEOUT); - } - - /** - * Private API - ensures the RpcClient is correctly open. - * @throws IOException if an error is encountered - */ - public void checkConsumer() throws IOException { - if (_consumer == null) { - throw new EOFException("RpcClient is closed"); - } - } - - /** - * Public API - cancels the consumer, thus deleting the temporary queue, and marks the RpcClient as closed. - * @throws IOException if an error is encountered - */ - public void close() throws IOException { - if (_consumer != null) { - _channel.basicCancel(_consumer.getConsumerTag()); - _consumer = null; - } - } - - /** - * Registers a consumer on the reply queue. - * @throws IOException if an error is encountered - * @return the newly created and registered consumer - */ - protected DefaultConsumer setupConsumer() throws IOException { - DefaultConsumer consumer = new DefaultConsumer(_channel) { - @Override - public void handleShutdownSignal(String consumerTag, - ShutdownSignalException signal) { - synchronized (_continuationMap) { - for (Entry> entry : _continuationMap.entrySet()) { - entry.getValue().set(signal); - } - _consumer = null; - } - } - - @Override - public void handleDelivery(String consumerTag, - Envelope envelope, - AMQP.BasicProperties properties, - byte[] body) - throws IOException { - synchronized (_continuationMap) { - String replyId = properties.getCorrelationId(); - BlockingCell blocker = _continuationMap.get(replyId); - _continuationMap.remove(replyId); - blocker.set(body); - } - } - }; - _channel.basicConsume("amq.rabbitmq.reply-to", true, consumer); - return consumer; - } - - public void publish(AMQP.BasicProperties props, byte[] message) - throws IOException - { - _channel.basicPublish(_exchange, _routingKey, props, message); - } - - public byte[] primitiveCall(AMQP.BasicProperties props, byte[] message) - throws IOException, ShutdownSignalException, TimeoutException - { - checkConsumer(); - BlockingCell k = new BlockingCell(); - synchronized (_continuationMap) { - _correlationId++; - String replyId = "" + _correlationId; - props = ((props==null) ? new AMQP.BasicProperties.Builder() : props.builder()) - .correlationId(replyId).replyTo("amq.rabbitmq.reply-to").build(); - _continuationMap.put(replyId, k); - } - publish(props, message); - Object reply = k.uninterruptibleGet(_timeout); - if (reply instanceof ShutdownSignalException) { - ShutdownSignalException sig = (ShutdownSignalException) reply; - ShutdownSignalException wrapper = - new ShutdownSignalException(sig.isHardError(), - sig.isInitiatedByApplication(), - sig.getReason(), - sig.getReference()); - wrapper.initCause(sig); - throw wrapper; - } else { - return (byte[]) reply; - } - } - - /** - * Perform a simple byte-array-based RPC roundtrip. - * @param message the byte array request message to send - * @return the byte array response received - * @throws ShutdownSignalException if the connection dies during our wait - * @throws IOException if an error is encountered - * @throws TimeoutException if a response is not received within the configured timeout - */ - public byte[] primitiveCall(byte[] message) - throws IOException, ShutdownSignalException, TimeoutException { - return primitiveCall(null, message); - } - - /** - * Perform a simple string-based RPC roundtrip. - * @param message the string request message to send - * @return the string response received - * @throws ShutdownSignalException if the connection dies during our wait - * @throws IOException if an error is encountered - * @throws TimeoutException if a timeout occurs before the response is received - */ - @SuppressWarnings("unused") - public String stringCall(String message) - throws IOException, ShutdownSignalException, TimeoutException - { - byte[] request; - try { - request = message.getBytes(StringRpcServer.STRING_ENCODING); - } catch (IOException _e) { - request = message.getBytes(); - } - byte[] reply = primitiveCall(request); - try { - return new String(reply, StringRpcServer.STRING_ENCODING); - } catch (IOException _e) { - return new String(reply); - } - } - - /** - * Perform an AMQP wire-protocol-table based RPC roundtrip

- * - * There are some restrictions on the values appearing in the table:
- * they must be of type {@link String}, {@link LongString}, {@link Integer}, {@link java.math.BigDecimal}, {@link Date}, - * or (recursively) a {@link Map} of the enclosing type. - * - * @param message the table to send - * @return the table received - * @throws ShutdownSignalException if the connection dies during our wait - * @throws IOException if an error is encountered - * @throws TimeoutException if a timeout occurs before a response is received - */ - public Map mapCall(Map message) - throws IOException, ShutdownSignalException, TimeoutException - { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - MethodArgumentWriter writer = new MethodArgumentWriter(new ValueWriter(new DataOutputStream(buffer))); - writer.writeTable(message); - writer.flush(); - byte[] reply = primitiveCall(buffer.toByteArray()); - MethodArgumentReader reader = - new MethodArgumentReader(new ValueReader(new DataInputStream(new ByteArrayInputStream(reply)))); - return reader.readTable(); - } - - /** - * Perform an AMQP wire-protocol-table based RPC roundtrip, first - * constructing the table from an array of alternating keys (in - * even-numbered elements, starting at zero) and values (in - * odd-numbered elements, starting at one)
- * Restrictions on value arguments apply as in {@link RpcClient#mapCall(Map)}. - * - * @param keyValuePairs alternating {key, value, key, value, ...} data to send - * @return the table received - * @throws ShutdownSignalException if the connection dies during our wait - * @throws IOException if an error is encountered - * @throws TimeoutException if a timeout occurs before a response is received - */ - public Map mapCall(Object[] keyValuePairs) - throws IOException, ShutdownSignalException, TimeoutException - { - Map message = new HashMap(); - for (int i = 0; i < keyValuePairs.length; i += 2) { - message.put((String) keyValuePairs[i], keyValuePairs[i + 1]); - } - return mapCall(message); - } - - /** - * Retrieve the channel. - * @return the channel to which this client is connected - */ - public Channel getChannel() { - return _channel; - } - - /** - * Retrieve the exchange. - * @return the exchange to which this client is connected - */ - public String getExchange() { - return _exchange; - } - - /** - * Retrieve the routing key. - * @return the routing key for messages to this client - */ - public String getRoutingKey() { - return _routingKey; - } - - /** - * Retrieve the continuation map. - * @return the map of objects to blocking cells for this client - */ - public Map> getContinuationMap() { - return _continuationMap; - } - - /** - * Retrieve the correlation id. - * @return the most recently used correlation id - */ - public int getCorrelationId() { - return _correlationId; - } - - /** - * Retrieve the consumer. - * @return an interface to the client's consumer object - */ - public Consumer getConsumer() { - return _consumer; - } -} - diff --git a/src/com/rabbitmq/client/RpcServer.java b/src/com/rabbitmq/client/RpcServer.java deleted file mode 100644 index 73ccceb4e8..0000000000 --- a/src/com/rabbitmq/client/RpcServer.java +++ /dev/null @@ -1,235 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import java.io.IOException; - -/** - * Class which manages a request queue for a simple RPC-style service. - * The class is agnostic about the format of RPC arguments / return values. -*/ -public class RpcServer { - /** Channel we are communicating on */ - private final Channel _channel; - /** Queue to receive requests from */ - private final String _queueName; - /** Boolean controlling the exit from the mainloop. */ - private boolean _mainloopRunning = true; - - /** Consumer attached to our request queue */ - private QueueingConsumer _consumer; - - /** - * Creates an RpcServer listening on a temporary exclusive - * autodelete queue. - */ - public RpcServer(Channel channel) - throws IOException - { - this(channel, null); - } - - /** - * If the passed-in queue name is null, creates a server-named - * temporary exclusive autodelete queue to use; otherwise expects - * the queue to have already been declared. - */ - public RpcServer(Channel channel, String queueName) - throws IOException - { - _channel = channel; - if (queueName == null || queueName.equals("")) { - _queueName = _channel.queueDeclare().getQueue(); - } else { - _queueName = queueName; - } - _consumer = setupConsumer(); - } - - /** - * Public API - cancels the consumer, thus deleting the queue, if - * it was a temporary queue, and marks the RpcServer as closed. - * @throws IOException if an error is encountered - */ - public void close() - throws IOException - { - if (_consumer != null) { - _channel.basicCancel(_consumer.getConsumerTag()); - _consumer = null; - } - terminateMainloop(); - } - - /** - * Registers a consumer on the reply queue. - * @throws IOException if an error is encountered - * @return the newly created and registered consumer - */ - protected QueueingConsumer setupConsumer() - throws IOException - { - QueueingConsumer consumer = new QueueingConsumer(_channel); - _channel.basicConsume(_queueName, consumer); - return consumer; - } - - /** - * Public API - main server loop. Call this to begin processing - * requests. Request processing will continue until the Channel - * (or its underlying Connection) is shut down, or until - * terminateMainloop() is called. - * - * Note that if the mainloop is blocked waiting for a request, the - * termination flag is not checked until a request is received, so - * a good time to call terminateMainloop() is during a request - * handler. - * - * @return the exception that signalled the Channel shutdown, or null for orderly shutdown - */ - public ShutdownSignalException mainloop() - throws IOException - { - try { - while (_mainloopRunning) { - QueueingConsumer.Delivery request; - try { - request = _consumer.nextDelivery(); - } catch (InterruptedException ie) { - continue; - } - processRequest(request); - _channel.basicAck(request.getEnvelope().getDeliveryTag(), false); - } - return null; - } catch (ShutdownSignalException sse) { - return sse; - } - } - - /** - * Call this method to terminate the mainloop. - * - * Note that if the mainloop is blocked waiting for a request, the - * termination flag is not checked until a request is received, so - * a good time to call terminateMainloop() is during a request - * handler. - */ - public void terminateMainloop() { - _mainloopRunning = false; - } - - /** - * Private API - Process a single request. Called from mainloop(). - */ - public void processRequest(QueueingConsumer.Delivery request) - throws IOException - { - AMQP.BasicProperties requestProperties = request.getProperties(); - String correlationId = requestProperties.getCorrelationId(); - String replyTo = requestProperties.getReplyTo(); - if (correlationId != null && replyTo != null) - { - AMQP.BasicProperties replyProperties - = new AMQP.BasicProperties.Builder().correlationId(correlationId).build(); - byte[] replyBody = handleCall(request, replyProperties); - _channel.basicPublish("", replyTo, replyProperties, replyBody); - } else { - handleCast(request); - } - } - - /** - * Lowest-level response method. Calls - * handleCall(AMQP.BasicProperties,byte[],AMQP.BasicProperties). - */ - public byte[] handleCall(QueueingConsumer.Delivery request, - AMQP.BasicProperties replyProperties) - { - return handleCall(request.getProperties(), - request.getBody(), - replyProperties); - } - - /** - * Mid-level response method. Calls - * handleCall(byte[],AMQP.BasicProperties). - */ - public byte[] handleCall(AMQP.BasicProperties requestProperties, - byte[] requestBody, - AMQP.BasicProperties replyProperties) - { - return handleCall(requestBody, replyProperties); - } - - /** - * High-level response method. Returns an empty response by - * default - override this (or other handleCall and handleCast - * methods) in subclasses. - */ - public byte[] handleCall(byte[] requestBody, - AMQP.BasicProperties replyProperties) - { - return new byte[0]; - } - - /** - * Lowest-level handler method. Calls - * handleCast(AMQP.BasicProperties,byte[]). - */ - public void handleCast(QueueingConsumer.Delivery request) - { - handleCast(request.getProperties(), request.getBody()); - } - - /** - * Mid-level handler method. Calls - * handleCast(byte[]). - */ - public void handleCast(AMQP.BasicProperties requestProperties, byte[] requestBody) - { - handleCast(requestBody); - } - - /** - * High-level handler method. Does nothing by default - override - * this (or other handleCast and handleCast methods) in - * subclasses. - */ - public void handleCast(byte[] requestBody) - { - // Does nothing. - } - - /** - * Retrieve the channel. - * @return the channel to which this server is connected - */ - public Channel getChannel() { - return _channel; - } - - /** - * Retrieve the queue name. - * @return the queue which this server is consuming from - */ - public String getQueueName() { - return _queueName; - } -} - diff --git a/src/com/rabbitmq/client/SaslConfig.java b/src/com/rabbitmq/client/SaslConfig.java deleted file mode 100644 index 87420cbd0e..0000000000 --- a/src/com/rabbitmq/client/SaslConfig.java +++ /dev/null @@ -1,26 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client; - -/** - * This interface represents a hook to allow you to control how exactly - * a sasl client is selected during authentication. - * @see com.rabbitmq.client.ConnectionFactory - */ -public interface SaslConfig { - SaslMechanism getSaslMechanism(String[] mechanisms); -} diff --git a/src/com/rabbitmq/client/SaslMechanism.java b/src/com/rabbitmq/client/SaslMechanism.java deleted file mode 100644 index 59b2538c83..0000000000 --- a/src/com/rabbitmq/client/SaslMechanism.java +++ /dev/null @@ -1,41 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client; - -import java.io.IOException; - -/** - * Our own view of a SASL authentication mechanism, introduced to remove a - * dependency on javax.security.sasl. - */ -public interface SaslMechanism { - /** - * The name of this mechanism (e.g. PLAIN) - * @return the name - */ - String getName(); - - /** - * Handle one round of challenge-response - * @param challenge the challenge this round, or null on first round. - * @param username name of user - * @param password for username - * @return response - * @throws IOException - */ - LongString handleChallenge(LongString challenge, String username, String password); -} diff --git a/src/com/rabbitmq/client/ShutdownListener.java b/src/com/rabbitmq/client/ShutdownListener.java deleted file mode 100644 index 0bfd890315..0000000000 --- a/src/com/rabbitmq/client/ShutdownListener.java +++ /dev/null @@ -1,35 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import java.util.EventListener; - -/** - * A ShutdownListener receives information about the shutdown of connections and - * channels. Note that when a connection is shut down, its associated channels are also - * considered shut down and their ShutdownListeners will be notified (with the same cause). - * Because of this, and the fact that channel ShutdownListeners execute in the connection's - * thread, attempting to make blocking calls on a connection inside the listener will - * lead to deadlock. - * - * @see ShutdownNotifier - * @see ShutdownSignalException - */ -public interface ShutdownListener extends EventListener { - public void shutdownCompleted(ShutdownSignalException cause); -} diff --git a/src/com/rabbitmq/client/SocketConfigurator.java b/src/com/rabbitmq/client/SocketConfigurator.java deleted file mode 100644 index ca553a1ed7..0000000000 --- a/src/com/rabbitmq/client/SocketConfigurator.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.rabbitmq.client; - -import java.io.IOException; -import java.net.Socket; - -public interface SocketConfigurator { - /** - * Provides a hook to insert custom configuration of the sockets - * used to connect to an AMQP server before they connect. - */ - void configure(Socket socket) throws IOException; -} diff --git a/src/com/rabbitmq/client/TopologyRecoveryException.java b/src/com/rabbitmq/client/TopologyRecoveryException.java deleted file mode 100644 index c220ab09e5..0000000000 --- a/src/com/rabbitmq/client/TopologyRecoveryException.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.rabbitmq.client; - -/** - * Indicates an exception thrown during topology recovery. - * - * @see com.rabbitmq.client.ConnectionFactory#setTopologyRecovery(boolean) - * @since 3.3.0 - */ -public class TopologyRecoveryException extends Exception { - public TopologyRecoveryException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/com/rabbitmq/client/UnexpectedFrameError.java b/src/com/rabbitmq/client/UnexpectedFrameError.java deleted file mode 100644 index 08df6d4d11..0000000000 --- a/src/com/rabbitmq/client/UnexpectedFrameError.java +++ /dev/null @@ -1,47 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -import com.rabbitmq.client.impl.Frame; - -/** - * Thrown when the command parser hits an unexpected frame type. - */ -public class UnexpectedFrameError extends Error { - private static final long serialVersionUID = 1L; - private final Frame _frame; - private final int _expectedFrameType; - - public UnexpectedFrameError(Frame frame, int expectedFrameType) { - super("Received frame: " + frame + ", expected type " + expectedFrameType); - _frame = frame; - _expectedFrameType = expectedFrameType; - } - - public static long getSerialVersionUID() { - return serialVersionUID; - } - - public Frame getReceivedFrame() { - return _frame; - } - - public int getExpectedFrameType() { - return _expectedFrameType; - } -} diff --git a/src/com/rabbitmq/client/impl/AMQBasicProperties.java b/src/com/rabbitmq/client/impl/AMQBasicProperties.java deleted file mode 100644 index 53acbe3e13..0000000000 --- a/src/com/rabbitmq/client/impl/AMQBasicProperties.java +++ /dev/null @@ -1,39 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.impl; - -import java.io.DataInputStream; -import java.io.IOException; - -import com.rabbitmq.client.BasicProperties; - -public abstract class AMQBasicProperties - extends AMQContentHeader implements BasicProperties { - - protected AMQBasicProperties() { - - } - - protected AMQBasicProperties(DataInputStream in) throws IOException { - super(in); - } - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } -} diff --git a/src/com/rabbitmq/client/impl/AMQChannel.java b/src/com/rabbitmq/client/impl/AMQChannel.java deleted file mode 100644 index 09a8f1fc57..0000000000 --- a/src/com/rabbitmq/client/impl/AMQChannel.java +++ /dev/null @@ -1,367 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.impl; - -import java.io.IOException; -import java.util.concurrent.TimeoutException; - -import com.rabbitmq.client.AlreadyClosedException; -import com.rabbitmq.client.Command; -import com.rabbitmq.client.Method; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ShutdownSignalException; -import com.rabbitmq.utility.BlockingValueOrException; - -/** - * Base class modelling an AMQ channel. Subclasses implement - * {@link com.rabbitmq.client.Channel#close} and - * {@link #processAsync processAsync()}, and may choose to override - * {@link #processShutdownSignal processShutdownSignal()} and - * {@link #rpc rpc()}. - * - * @see ChannelN - * @see Connection - */ -public abstract class AMQChannel extends ShutdownNotifierComponent { - /** - * Protected; used instead of synchronizing on the channel itself, - * so that clients can themselves use the channel to synchronize - * on. - */ - protected final Object _channelMutex = new Object(); - - /** The connection this channel is associated with. */ - private final AMQConnection _connection; - - /** This channel's channel number. */ - private final int _channelNumber; - - /** Command being assembled */ - private AMQCommand _command = new AMQCommand(); - - /** The current outstanding RPC request, if any. (Could become a queue in future.) */ - private RpcContinuation _activeRpc = null; - - /** Whether transmission of content-bearing methods should be blocked */ - public boolean _blockContent = false; - - /** - * Construct a channel on the given connection, with the given channel number. - * @param connection the underlying connection for this channel - * @param channelNumber the allocated reference number for this channel - */ - public AMQChannel(AMQConnection connection, int channelNumber) { - this._connection = connection; - this._channelNumber = channelNumber; - } - - /** - * Public API - Retrieves this channel's channel number. - * @return the channel number - */ - public int getChannelNumber() { - return _channelNumber; - } - - /** - * Private API - When the Connection receives a Frame for this - * channel, it passes it to this method. - * @param frame the incoming frame - * @throws IOException if an error is encountered - */ - public void handleFrame(Frame frame) throws IOException { - AMQCommand command = _command; - if (command.handleFrame(frame)) { // a complete command has rolled off the assembly line - _command = new AMQCommand(); // prepare for the next one - handleCompleteInboundCommand(command); - } - } - - /** - * Placeholder until we address bug 15786 (implementing a proper exception hierarchy). - * In the meantime, this at least won't throw away any information from the wrapped exception. - * @param ex the exception to wrap - * @return the wrapped exception - */ - public static IOException wrap(ShutdownSignalException ex) { - return wrap(ex, null); - } - - public static IOException wrap(ShutdownSignalException ex, String message) { - IOException ioe = new IOException(message); - ioe.initCause(ex); - return ioe; - } - - /** - * Placeholder until we address bug 15786 (implementing a proper exception hierarchy). - */ - public AMQCommand exnWrappingRpc(Method m) - throws IOException - { - try { - return privateRpc(m); - } catch (AlreadyClosedException ace) { - // Do not wrap it since it means that connection/channel - // was closed in some action in the past - throw ace; - } catch (ShutdownSignalException ex) { - throw wrap(ex); - } - } - - /** - * Private API - handle a command which has been assembled - * @throws IOException if there's any problem - * - * @param command the incoming command - * @throws IOException - */ - public void handleCompleteInboundCommand(AMQCommand command) throws IOException { - // First, offer the command to the asynchronous-command - // handling mechanism, which gets to act as a filter on the - // incoming command stream. If processAsync() returns true, - // the command has been dealt with by the filter and so should - // not be processed further. It will return true for - // asynchronous commands (deliveries/returns/other events), - // and false for commands that should be passed on to some - // waiting RPC continuation. - if (!processAsync(command)) { - // The filter decided not to handle/consume the command, - // so it must be some reply to an earlier RPC. - nextOutstandingRpc().handleCommand(command); - markRpcFinished(); - } - } - - public void enqueueRpc(RpcContinuation k) - { - synchronized (_channelMutex) { - boolean waitClearedInterruptStatus = false; - while (_activeRpc != null) { - try { - _channelMutex.wait(); - } catch (InterruptedException e) { - waitClearedInterruptStatus = true; - } - } - if (waitClearedInterruptStatus) { - Thread.currentThread().interrupt(); - } - _activeRpc = k; - } - } - - public boolean isOutstandingRpc() - { - synchronized (_channelMutex) { - return (_activeRpc != null); - } - } - - public RpcContinuation nextOutstandingRpc() - { - synchronized (_channelMutex) { - RpcContinuation result = _activeRpc; - _activeRpc = null; - _channelMutex.notifyAll(); - return result; - } - } - - protected void markRpcFinished() { - // no-op - } - - public void ensureIsOpen() - throws AlreadyClosedException - { - if (!isOpen()) { - throw new AlreadyClosedException(getCloseReason()); - } - } - - /** - * Protected API - sends a {@link Method} to the broker and waits for the - * next in-bound Command from the broker: only for use from - * non-connection-MainLoop threads! - */ - public AMQCommand rpc(Method m) - throws IOException, ShutdownSignalException - { - return privateRpc(m); - } - - private AMQCommand privateRpc(Method m) - throws IOException, ShutdownSignalException - { - SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation(); - rpc(m, k); - // At this point, the request method has been sent, and we - // should wait for the reply to arrive. - // - // Calling getReply() on the continuation puts us to sleep - // until the connection's reader-thread throws the reply over - // the fence. - return k.getReply(); - } - - public void rpc(Method m, RpcContinuation k) - throws IOException - { - synchronized (_channelMutex) { - ensureIsOpen(); - quiescingRpc(m, k); - } - } - - public void quiescingRpc(Method m, RpcContinuation k) - throws IOException - { - synchronized (_channelMutex) { - enqueueRpc(k); - quiescingTransmit(m); - } - } - - /** - * Protected API - called by nextCommand to check possibly handle an incoming Command before it is returned to the caller of nextCommand. If this method - * returns true, the command is considered handled and is not passed back to nextCommand's caller; if it returns false, nextCommand returns the command as - * usual. This is used in subclasses to implement handling of Basic.Return and Basic.Deliver messages, as well as Channel.Close and Connection.Close. - * @param command the command to handle asynchronously - * @return true if we handled the command; otherwise the caller should consider it "unhandled" - */ - public abstract boolean processAsync(Command command) throws IOException; - - @Override public String toString() { - return "AMQChannel(" + _connection + "," + _channelNumber + ")"; - } - - /** - * Protected API - respond, in the driver thread, to a {@link ShutdownSignalException}. - * @param signal the signal to handle - * @param ignoreClosed the flag indicating whether to ignore the AlreadyClosedException - * thrown when the channel is already closed - * @param notifyRpc the flag indicating whether any remaining rpc continuation should be - * notified with the given signal - */ - public void processShutdownSignal(ShutdownSignalException signal, - boolean ignoreClosed, - boolean notifyRpc) { - try { - synchronized (_channelMutex) { - if (!setShutdownCauseIfOpen(signal)) { - if (!ignoreClosed) - throw new AlreadyClosedException(getCloseReason()); - } - - _channelMutex.notifyAll(); - } - } finally { - if (notifyRpc) - notifyOutstandingRpc(signal); - } - } - - public void notifyOutstandingRpc(ShutdownSignalException signal) { - RpcContinuation k = nextOutstandingRpc(); - if (k != null) { - k.handleShutdownSignal(signal); - } - } - - public void transmit(Method m) throws IOException { - synchronized (_channelMutex) { - transmit(new AMQCommand(m)); - } - } - - public void transmit(AMQCommand c) throws IOException { - synchronized (_channelMutex) { - ensureIsOpen(); - quiescingTransmit(c); - } - } - - public void quiescingTransmit(Method m) throws IOException { - synchronized (_channelMutex) { - quiescingTransmit(new AMQCommand(m)); - } - } - - public void quiescingTransmit(AMQCommand c) throws IOException { - synchronized (_channelMutex) { - if (c.getMethod().hasContent()) { - while (_blockContent) { - try { - _channelMutex.wait(); - } catch (InterruptedException e) {} - - // This is to catch a situation when the thread wakes up during - // shutdown. Currently, no command that has content is allowed - // to send anything in a closing state. - ensureIsOpen(); - } - } - c.transmit(this); - } - } - - public AMQConnection getConnection() { - return _connection; - } - - public interface RpcContinuation { - void handleCommand(AMQCommand command); - void handleShutdownSignal(ShutdownSignalException signal); - } - - public static abstract class BlockingRpcContinuation implements RpcContinuation { - public final BlockingValueOrException _blocker = - new BlockingValueOrException(); - - public void handleCommand(AMQCommand command) { - _blocker.setValue(transformReply(command)); - } - - public void handleShutdownSignal(ShutdownSignalException signal) { - _blocker.setException(signal); - } - - public T getReply() throws ShutdownSignalException - { - return _blocker.uninterruptibleGetValue(); - } - - public T getReply(int timeout) - throws ShutdownSignalException, TimeoutException - { - return _blocker.uninterruptibleGetValue(timeout); - } - - public abstract T transformReply(AMQCommand command); - } - - public static class SimpleBlockingRpcContinuation - extends BlockingRpcContinuation - { - public AMQCommand transformReply(AMQCommand command) { - return command; - } - } -} diff --git a/src/com/rabbitmq/client/impl/ClientVersion.java.in b/src/com/rabbitmq/client/impl/ClientVersion.java.in deleted file mode 100644 index 8eb0a13fe3..0000000000 --- a/src/com/rabbitmq/client/impl/ClientVersion.java.in +++ /dev/null @@ -1,25 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.impl; - -/** - * Publicly available Client Version information - */ -public class ClientVersion { - /** Full version string */ - public static final String VERSION = "@VERSION@"; -} diff --git a/src/com/rabbitmq/client/impl/ConnectionParams.java b/src/com/rabbitmq/client/impl/ConnectionParams.java deleted file mode 100644 index 6a28df4087..0000000000 --- a/src/com/rabbitmq/client/impl/ConnectionParams.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.rabbitmq.client.impl; - -import com.rabbitmq.client.ExceptionHandler; -import com.rabbitmq.client.SaslConfig; - -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadFactory; - -public class ConnectionParams { - private final String username; - private final String password; - private final ExecutorService executor; - private final String virtualHost; - private final Map clientProperties; - private final int requestedFrameMax; - private final int requestedChannelMax; - private final int requestedHeartbeat; - private final int shutdownTimeout; - private final SaslConfig saslConfig; - private final long networkRecoveryInterval; - private final boolean topologyRecovery; - - private final ExceptionHandler exceptionHandler; - private final ThreadFactory threadFactory; - - /** - * @param username name used to establish connection - * @param password for username - * @param executor thread pool service for consumer threads for channels on this connection - * @param virtualHost virtual host of this connection - * @param clientProperties client info used in negotiating with the server - * @param requestedFrameMax max size of frame offered - * @param requestedChannelMax max number of channels offered - * @param requestedHeartbeat heart-beat in seconds offered - * @param saslConfig sasl configuration hook - * @param networkRecoveryInterval interval used when recovering from network failure - * @param topologyRecovery should topology (queues, exchanges, bindings, consumers) recovery be performed? - * @param threadFactory - * @param exceptionHandler - */ - public ConnectionParams(String username, String password, ExecutorService executor, - String virtualHost, Map clientProperties, - int requestedFrameMax, int requestedChannelMax, int requestedHeartbeat, - int shutdownTimeout, SaslConfig saslConfig, long networkRecoveryInterval, - boolean topologyRecovery, ExceptionHandler exceptionHandler, ThreadFactory threadFactory) { - this.username = username; - this.password = password; - this.executor = executor; - this.virtualHost = virtualHost; - this.clientProperties = clientProperties; - this.requestedFrameMax = requestedFrameMax; - this.requestedChannelMax = requestedChannelMax; - this.requestedHeartbeat = requestedHeartbeat; - this.shutdownTimeout = shutdownTimeout; - this.saslConfig = saslConfig; - this.networkRecoveryInterval = networkRecoveryInterval; - this.topologyRecovery = topologyRecovery; - this.exceptionHandler = exceptionHandler; - this.threadFactory = threadFactory; - } - - public String getUsername() { - return username; - } - - public String getPassword() { - return password; - } - - public ExecutorService getExecutor() { - return executor; - } - - public String getVirtualHost() { - return virtualHost; - } - - public Map getClientProperties() { - return clientProperties; - } - - public int getRequestedFrameMax() { - return requestedFrameMax; - } - - public int getRequestedChannelMax() { - return requestedChannelMax; - } - - public int getRequestedHeartbeat() { - return requestedHeartbeat; - } - - public int getShutdownTimeout() { - return shutdownTimeout; - } - - public SaslConfig getSaslConfig() { - return saslConfig; - } - - public ExceptionHandler getExceptionHandler() { - return exceptionHandler; - } - - public long getNetworkRecoveryInterval() { - return networkRecoveryInterval; - } - - public boolean isTopologyRecoveryEnabled() { - return topologyRecovery; - } - - public ThreadFactory getThreadFactory() { - return threadFactory; - } -} diff --git a/src/com/rabbitmq/client/impl/DefaultExceptionHandler.java b/src/com/rabbitmq/client/impl/DefaultExceptionHandler.java deleted file mode 100644 index c07e7256fa..0000000000 --- a/src/com/rabbitmq/client/impl/DefaultExceptionHandler.java +++ /dev/null @@ -1,135 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.impl; - -import java.io.IOException; -import java.net.ConnectException; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.AlreadyClosedException; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Consumer; -import com.rabbitmq.client.ExceptionHandler; -import com.rabbitmq.client.TopologyRecoveryException; - -/** - * Default implementation of {@link com.rabbitmq.client.ExceptionHandler} used by {@link AMQConnection}. - */ -public class DefaultExceptionHandler implements ExceptionHandler { - public void handleUnexpectedConnectionDriverException(Connection conn, Throwable exception) { - // TODO: Log this somewhere, just in case we have a bug like - // 16272 where exceptions aren't being propagated properly - // again. - - //System.err.println("DefaultExceptionHandler:"); - //exception.printStackTrace(); - } - - public void handleReturnListenerException(Channel channel, Throwable exception) { - handleChannelKiller(channel, exception, "ReturnListener.handleReturn"); - } - - public void handleFlowListenerException(Channel channel, Throwable exception) { - handleChannelKiller(channel, exception, "FlowListener.handleFlow"); - } - - public void handleConfirmListenerException(Channel channel, Throwable exception) { - handleChannelKiller(channel, exception, "ConfirmListener.handle{N,A}ck"); - } - - public void handleBlockedListenerException(Connection connection, Throwable exception) { - handleConnectionKiller(connection, exception, "BlockedListener"); - } - - public void handleConsumerException(Channel channel, Throwable exception, - Consumer consumer, String consumerTag, - String methodName) - { - handleChannelKiller(channel, exception, "Consumer " + consumer - + " (" + consumerTag + ")" - + " method " + methodName - + " for channel " + channel); - } - - /** - * @since 3.3.0 - */ - public void handleConnectionRecoveryException(Connection conn, Throwable exception) { - // ignore java.net.ConnectException as those are - // expected during recovery and will only produce noisy - // traces - if (exception instanceof ConnectException) { - // no-op - } else { - System.err.println("Caught an exception during connection recovery!"); - exception.printStackTrace(System.err); - } - } - - /** - * @since 3.3.0 - */ - public void handleChannelRecoveryException(Channel ch, Throwable exception) { - System.err.println("Caught an exception when recovering channel " + ch.getChannelNumber()); - exception.printStackTrace(System.err); - } - - /** - * @since 3.3.0 - */ - public void handleTopologyRecoveryException(Connection conn, Channel ch, TopologyRecoveryException exception) { - System.err.println("Caught an exception when recovering topology " + exception.getMessage()); - exception.printStackTrace(System.err); - } - - protected void handleChannelKiller(Channel channel, Throwable exception, String what) { - // TODO: log the exception - System.err.println("DefaultExceptionHandler: " + what + " threw an exception for channel " - + channel + ":"); - exception.printStackTrace(); - try { - channel.close(AMQP.REPLY_SUCCESS, "Closed due to exception from " + what); - } catch (AlreadyClosedException ace) { - // noop - } catch (IOException ioe) { - // TODO: log the failure - System.err.println("Failure during close of channel " + channel + " after " + exception - + ":"); - ioe.printStackTrace(); - channel.getConnection().abort(AMQP.INTERNAL_ERROR, "Internal error closing channel for " + what); - } - } - - protected void handleConnectionKiller(Connection connection, Throwable exception, String what) { - // TODO: log the exception - System.err.println("DefaultExceptionHandler: " + what + " threw an exception for connection " - + connection + ":"); - exception.printStackTrace(); - try { - connection.close(AMQP.REPLY_SUCCESS, "Closed due to exception from " + what); - } catch (AlreadyClosedException ace) { - // noop - } catch (IOException ioe) { - // TODO: log the failure - System.err.println("Failure during close of connection " + connection + " after " + exception - + ":"); - ioe.printStackTrace(); - connection.abort(AMQP.INTERNAL_ERROR, "Internal error closing connection for " + what); - } - } -} diff --git a/src/com/rabbitmq/client/impl/Environment.java b/src/com/rabbitmq/client/impl/Environment.java deleted file mode 100644 index a387b195a0..0000000000 --- a/src/com/rabbitmq/client/impl/Environment.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.rabbitmq.client.impl; - -import java.util.concurrent.ThreadFactory; - -/** - * Infers information about the execution environment, e.g. - * security permissions. - */ -class Environment { - public static boolean isAllowedToModifyThreads() { - try { - SecurityManager sm = System.getSecurityManager(); - if(sm != null) { - sm.checkPermission(new RuntimePermission("modifyThread")); - sm.checkPermission(new RuntimePermission("modifyThreadGroup")); - } - return true; - } catch (SecurityException se) { - return false; - } - } - - public static Thread newThread(ThreadFactory factory, Runnable runnable, String name) { - Thread t = factory.newThread(runnable); - if(isAllowedToModifyThreads()) { - t.setName(name); - } - return t; - } - - public static Thread newThread(ThreadFactory factory, Runnable runnable, String name, boolean isDaemon) { - Thread t = newThread(factory, runnable, name); - if(isAllowedToModifyThreads()) { - t.setDaemon(isDaemon); - } - return t; - } -} diff --git a/src/com/rabbitmq/client/impl/ExternalMechanism.java b/src/com/rabbitmq/client/impl/ExternalMechanism.java deleted file mode 100644 index fce3ab1df5..0000000000 --- a/src/com/rabbitmq/client/impl/ExternalMechanism.java +++ /dev/null @@ -1,33 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.impl; - -import com.rabbitmq.client.LongString; -import com.rabbitmq.client.SaslMechanism; - -/** - * The EXTERNAL auth mechanism - */ -public class ExternalMechanism implements SaslMechanism { - public String getName() { - return "EXTERNAL"; - } - - public LongString handleChallenge(LongString challenge, String username, String password) { - return LongStringHelper.asLongString(""); - } -} diff --git a/src/com/rabbitmq/client/impl/FrameHandlerFactory.java b/src/com/rabbitmq/client/impl/FrameHandlerFactory.java deleted file mode 100644 index dc95fac7e9..0000000000 --- a/src/com/rabbitmq/client/impl/FrameHandlerFactory.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.rabbitmq.client.impl; - -import com.rabbitmq.client.Address; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.SocketConfigurator; - -import javax.net.SocketFactory; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; - -public class FrameHandlerFactory { - private final int connectionTimeout; - private final SocketFactory factory; - private final SocketConfigurator configurator; - private final boolean ssl; - - public FrameHandlerFactory(int connectionTimeout, SocketFactory factory, SocketConfigurator configurator, boolean ssl) { - this.connectionTimeout = connectionTimeout; - this.factory = factory; - this.configurator = configurator; - this.ssl = ssl; - } - - public FrameHandler create(Address addr) throws IOException { - String hostName = addr.getHost(); - int portNumber = ConnectionFactory.portOrDefault(addr.getPort(), ssl); - Socket socket = null; - try { - socket = factory.createSocket(); - configurator.configure(socket); - socket.connect(new InetSocketAddress(hostName, portNumber), - connectionTimeout); - return create(socket); - } catch (IOException ioe) { - quietTrySocketClose(socket); - throw ioe; - } - } - - public FrameHandler create(Socket sock) throws IOException - { - return new SocketFrameHandler(sock); - } - - private static void quietTrySocketClose(Socket socket) { - if (socket != null) - try { socket.close(); } catch (Exception _e) {/*ignore exceptions*/} - } -} diff --git a/src/com/rabbitmq/client/impl/NetworkConnection.java b/src/com/rabbitmq/client/impl/NetworkConnection.java deleted file mode 100644 index ec12df8ca5..0000000000 --- a/src/com/rabbitmq/client/impl/NetworkConnection.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.rabbitmq.client.impl; - -import java.net.InetAddress; - -public interface NetworkConnection { - - /** - * Retrieve the local host. - * @return the client socket address. - */ - InetAddress getLocalAddress(); - - /** - * Retrieve the local port number. - * @return the client socket port number - */ - int getLocalPort(); - - /** Retrieve address of peer. */ - InetAddress getAddress(); - - /** Retrieve port number of peer. */ - int getPort(); -} diff --git a/src/com/rabbitmq/client/impl/PlainMechanism.java b/src/com/rabbitmq/client/impl/PlainMechanism.java deleted file mode 100644 index b134dd5c0f..0000000000 --- a/src/com/rabbitmq/client/impl/PlainMechanism.java +++ /dev/null @@ -1,36 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.impl; - -import com.rabbitmq.client.LongString; -import com.rabbitmq.client.SaslMechanism; - -/** - * The PLAIN auth mechanism - */ -public class PlainMechanism implements SaslMechanism { - public String getName() { - return "PLAIN"; - } - - public LongString handleChallenge(LongString challenge, - String username, - String password) { - return LongStringHelper.asLongString("\0" + username + - "\0" + password); - } -} diff --git a/src/com/rabbitmq/client/impl/SocketFrameHandler.java b/src/com/rabbitmq/client/impl/SocketFrameHandler.java deleted file mode 100644 index 004cc2b32d..0000000000 --- a/src/com/rabbitmq/client/impl/SocketFrameHandler.java +++ /dev/null @@ -1,159 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.impl; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.net.SocketException; - -import com.rabbitmq.client.AMQP; - -/** - * A socket-based frame handler. - */ - -public class SocketFrameHandler implements FrameHandler { - /** The underlying socket */ - private final Socket _socket; - - /** Socket's inputstream - data from the broker - synchronized on */ - private final DataInputStream _inputStream; - - /** Socket's outputstream - data to the broker - synchronized on */ - private final DataOutputStream _outputStream; - - /** Time to linger before closing the socket forcefully. */ - public static final int SOCKET_CLOSING_TIMEOUT = 1; - - /** - * @param socket the socket to use - */ - public SocketFrameHandler(Socket socket) throws IOException { - _socket = socket; - - _inputStream = new DataInputStream(new BufferedInputStream(socket.getInputStream())); - _outputStream = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); - } - - public InetAddress getAddress() { - return _socket.getInetAddress(); - } - - public InetAddress getLocalAddress() { - return _socket.getLocalAddress(); - } - - // For testing only - public DataInputStream getInputStream() { - return _inputStream; - } - - public int getPort() { - return _socket.getPort(); - } - - public int getLocalPort() { - return _socket.getLocalPort(); - } - - public void setTimeout(int timeoutMs) - throws SocketException - { - _socket.setSoTimeout(timeoutMs); - } - - public int getTimeout() - throws SocketException - { - return _socket.getSoTimeout(); - } - - /** - * Write a 0-8-style connection header to the underlying socket, - * containing the specified version information, kickstarting the - * AMQP protocol version negotiation process. - * - * @param major major protocol version number - * @param minor minor protocol version number - * @throws IOException if there is a problem accessing the connection - * @see #sendHeader() - */ - public void sendHeader(int major, int minor) throws IOException { - synchronized (_outputStream) { - _outputStream.write("AMQP".getBytes("US-ASCII")); - _outputStream.write(1); - _outputStream.write(1); - _outputStream.write(major); - _outputStream.write(minor); - _outputStream.flush(); - } - } - - /** - * Write a 0-9-1-style connection header to the underlying socket, - * containing the specified version information, kickstarting the - * AMQP protocol version negotiation process. - * - * @param major major protocol version number - * @param minor minor protocol version number - * @param revision protocol revision number - * @throws IOException if there is a problem accessing the connection - * @see #sendHeader() - */ - public void sendHeader(int major, int minor, int revision) throws IOException { - synchronized (_outputStream) { - _outputStream.write("AMQP".getBytes("US-ASCII")); - _outputStream.write(0); - _outputStream.write(major); - _outputStream.write(minor); - _outputStream.write(revision); - _outputStream.flush(); - } - } - - public void sendHeader() throws IOException { - sendHeader(AMQP.PROTOCOL.MAJOR, AMQP.PROTOCOL.MINOR, AMQP.PROTOCOL.REVISION); - } - - public Frame readFrame() throws IOException { - synchronized (_inputStream) { - return Frame.readFrom(_inputStream); - } - } - - public void writeFrame(Frame frame) throws IOException { - synchronized (_outputStream) { - frame.writeTo(_outputStream); - } - } - - public void flush() throws IOException { - _outputStream.flush(); - } - - @SuppressWarnings("unused") - public void close() { - try { _socket.setSoLinger(true, SOCKET_CLOSING_TIMEOUT); } catch (Exception _e) {} - try { flush(); } catch (Exception _e) {} - try { _socket.close(); } catch (Exception _e) {} - } -} diff --git a/src/com/rabbitmq/client/impl/UnknownChannelException.java b/src/com/rabbitmq/client/impl/UnknownChannelException.java deleted file mode 100644 index 4a0de7eb3f..0000000000 --- a/src/com/rabbitmq/client/impl/UnknownChannelException.java +++ /dev/null @@ -1,34 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.impl; - -class UnknownChannelException extends RuntimeException { - /** Default for non-checking. */ - private static final long serialVersionUID = 1L; - private final int channelNumber; - - public UnknownChannelException(int channelNumber) { - super("Unknown channel number " + channelNumber); - this.channelNumber = channelNumber; - } - - public int getChannelNumber() { - return channelNumber; - } - -} diff --git a/src/com/rabbitmq/client/impl/package.html b/src/com/rabbitmq/client/impl/package.html deleted file mode 100644 index 20ff0ce857..0000000000 --- a/src/com/rabbitmq/client/impl/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - -Implementations of interfaces specified in the client API, and their supporting classes. - - - diff --git a/src/com/rabbitmq/client/impl/recovery/AutorecoveringChannel.java b/src/com/rabbitmq/client/impl/recovery/AutorecoveringChannel.java deleted file mode 100644 index 5225f06921..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/AutorecoveringChannel.java +++ /dev/null @@ -1,589 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Command; -import com.rabbitmq.client.ConfirmListener; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Consumer; -import com.rabbitmq.client.FlowListener; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.Method; -import com.rabbitmq.client.Recoverable; -import com.rabbitmq.client.RecoveryListener; -import com.rabbitmq.client.ReturnListener; -import com.rabbitmq.client.ShutdownListener; -import com.rabbitmq.client.ShutdownSignalException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeoutException; - -/** - * {@link com.rabbitmq.client.Channel} implementation that is automatically - * recovered during connection recovery. - * - * @since 3.3.0 - */ -public class AutorecoveringChannel implements Channel, Recoverable { - private RecoveryAwareChannelN delegate; - private AutorecoveringConnection connection; - private final List shutdownHooks = new ArrayList(); - private final List recoveryListeners = new ArrayList(); - private final List returnListeners = new ArrayList(); - private final List confirmListeners = new ArrayList(); - private final List flowListeners = new ArrayList(); - private int prefetchCountConsumer; - private int prefetchCountGlobal; - private boolean usesPublisherConfirms; - private boolean usesTransactions; - - public AutorecoveringChannel(AutorecoveringConnection connection, RecoveryAwareChannelN delegate) { - this.connection = connection; - this.delegate = delegate; - } - - public int getChannelNumber() { - return delegate.getChannelNumber(); - } - - public Connection getConnection() { - return delegate.getConnection(); - } - - public Channel getDelegate() { - return delegate; - } - - public void close() throws IOException { - try { - delegate.close(); - } finally { - this.connection.unregisterChannel(this); - } - } - - public void close(int closeCode, String closeMessage) throws IOException { - try { - delegate.close(closeCode, closeMessage); - } finally { - this.connection.unregisterChannel(this); - } - } - - public boolean flowBlocked() { - return delegate.flowBlocked(); - } - - public void abort() throws IOException { - delegate.abort(); - } - - public void abort(int closeCode, String closeMessage) throws IOException { - delegate.abort(closeCode, closeMessage); - } - - public void addReturnListener(ReturnListener listener) { - this.returnListeners.add(listener); - delegate.addReturnListener(listener); - } - - public boolean removeReturnListener(ReturnListener listener) { - this.returnListeners.remove(listener); - return delegate.removeReturnListener(listener); - } - - public void clearReturnListeners() { - this.returnListeners.clear(); - delegate.clearReturnListeners(); - } - - public void addFlowListener(FlowListener listener) { - this.flowListeners.add(listener); - delegate.addFlowListener(listener); - } - - public boolean removeFlowListener(FlowListener listener) { - this.flowListeners.remove(listener); - return delegate.removeFlowListener(listener); - } - - public void clearFlowListeners() { - this.flowListeners.clear(); - delegate.clearFlowListeners(); - } - - public void addConfirmListener(ConfirmListener listener) { - this.confirmListeners.add(listener); - delegate.addConfirmListener(listener); - } - - public boolean removeConfirmListener(ConfirmListener listener) { - this.confirmListeners.remove(listener); - return delegate.removeConfirmListener(listener); - } - - public void clearConfirmListeners() { - this.confirmListeners.clear(); - delegate.clearConfirmListeners(); - } - - public Consumer getDefaultConsumer() { - return delegate.getDefaultConsumer(); - } - - public void setDefaultConsumer(Consumer consumer) { - delegate.setDefaultConsumer(consumer); - } - - public void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException { - if (global) { - this.prefetchCountGlobal = prefetchCount; - } else { - this.prefetchCountConsumer = prefetchCount; - } - - delegate.basicQos(prefetchSize, prefetchCount, global); - } - - public void basicQos(int prefetchCount) throws IOException { - basicQos(0, prefetchCount, false); - } - - public void basicQos(int prefetchCount, boolean global) throws IOException { - basicQos(0, prefetchCount, global); - } - - public void basicPublish(String exchange, String routingKey, AMQP.BasicProperties props, byte[] body) throws IOException { - delegate.basicPublish(exchange, routingKey, props, body); - } - - public void basicPublish(String exchange, String routingKey, boolean mandatory, AMQP.BasicProperties props, byte[] body) throws IOException { - delegate.basicPublish(exchange, routingKey, mandatory, props, body); - } - - public void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, AMQP.BasicProperties props, byte[] body) throws IOException { - delegate.basicPublish(exchange, routingKey, mandatory, immediate, props, body); - } - - public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type) throws IOException { - return exchangeDeclare(exchange, type, false, false, null); - } - - public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable) throws IOException { - return exchangeDeclare(exchange, type, durable, false, null); - } - - public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map arguments) throws IOException { - return exchangeDeclare(exchange, type, durable, autoDelete, false, arguments); - } - - public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map arguments) throws IOException { - final AMQP.Exchange.DeclareOk ok = delegate.exchangeDeclare(exchange, type, durable, autoDelete, internal, arguments); - RecordedExchange x = new RecordedExchange(this, exchange). - type(type). - durable(durable). - autoDelete(autoDelete). - arguments(arguments); - recordExchange(exchange, x); - return ok; - } - - @Override - public void exchangeDeclareNoWait(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map arguments) throws IOException { - RecordedExchange x = new RecordedExchange(this, exchange). - type(type). - durable(durable). - autoDelete(autoDelete). - arguments(arguments); - recordExchange(exchange, x); - delegate.exchangeDeclareNoWait(exchange, type, durable, autoDelete, internal, arguments); - } - - public AMQP.Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException { - return delegate.exchangeDeclarePassive(name); - } - - public AMQP.Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) throws IOException { - deleteRecordedExchange(exchange); - return delegate.exchangeDelete(exchange, ifUnused); - } - - public void exchangeDeleteNoWait(String exchange, boolean ifUnused) throws IOException { - deleteRecordedExchange(exchange); - delegate.exchangeDeleteNoWait(exchange, ifUnused); - } - - public AMQP.Exchange.DeleteOk exchangeDelete(String exchange) throws IOException { - return exchangeDelete(exchange, false); - } - - public AMQP.Exchange.BindOk exchangeBind(String destination, String source, String routingKey) throws IOException { - return exchangeBind(destination, source, routingKey, null); - } - - public AMQP.Exchange.BindOk exchangeBind(String destination, String source, String routingKey, Map arguments) throws IOException { - final AMQP.Exchange.BindOk ok = delegate.exchangeBind(destination, source, routingKey, arguments); - recordExchangeBinding(destination, source, routingKey, arguments); - return ok; - } - - public void exchangeBindNoWait(String destination, String source, String routingKey, Map arguments) throws IOException { - delegate.exchangeBindNoWait(destination, source, routingKey, arguments); - recordExchangeBinding(destination, source, routingKey, arguments); - } - - public AMQP.Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey) throws IOException { - return exchangeUnbind(destination, source, routingKey, null); - } - - public AMQP.Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey, Map arguments) throws IOException { - deleteRecordedExchangeBinding(destination, source, routingKey, arguments); - this.maybeDeleteRecordedAutoDeleteExchange(source); - return delegate.exchangeUnbind(destination, source, routingKey, arguments); - } - - public void exchangeUnbindNoWait(String destination, String source, String routingKey, Map arguments) throws IOException { - delegate.exchangeUnbindNoWait(destination, source, routingKey, arguments); - deleteRecordedExchangeBinding(destination, source, routingKey, arguments); - } - - public AMQP.Queue.DeclareOk queueDeclare() throws IOException { - return queueDeclare("", false, true, true, null); - } - - public AMQP.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments) throws IOException { - final AMQP.Queue.DeclareOk ok = delegate.queueDeclare(queue, durable, exclusive, autoDelete, arguments); - RecordedQueue q = new RecordedQueue(this, ok.getQueue()). - durable(durable). - exclusive(exclusive). - autoDelete(autoDelete). - arguments(arguments); - if (queue.equals(RecordedQueue.EMPTY_STRING)) { - q.serverNamed(true); - } - recordQueue(ok, q); - return ok; - } - - public void queueDeclareNoWait(String queue, - boolean durable, - boolean exclusive, - boolean autoDelete, - Map arguments) throws IOException { - RecordedQueue meta = new RecordedQueue(this, queue). - durable(durable). - exclusive(exclusive). - autoDelete(autoDelete). - arguments(arguments); - delegate.queueDeclareNoWait(queue, durable, exclusive, autoDelete, arguments); - recordQueue(queue, meta); - - } - - public AMQP.Queue.DeclareOk queueDeclarePassive(String queue) throws IOException { - return delegate.queueDeclarePassive(queue); - } - - public AMQP.Queue.DeleteOk queueDelete(String queue) throws IOException { - return queueDelete(queue, false, false); - } - - public AMQP.Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty) throws IOException { - deleteRecordedQueue(queue); - return delegate.queueDelete(queue, ifUnused, ifEmpty); - } - - public void queueDeleteNoWait(String queue, boolean ifUnused, boolean ifEmpty) throws IOException { - deleteRecordedQueue(queue); - delegate.queueDeleteNoWait(queue, ifUnused, ifEmpty); - } - - public AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOException { - return queueBind(queue, exchange, routingKey, null); - } - - public AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map arguments) throws IOException { - AMQP.Queue.BindOk ok = delegate.queueBind(queue, exchange, routingKey, arguments); - recordQueueBinding(queue, exchange, routingKey, arguments); - return ok; - } - - public void queueBindNoWait(String queue, String exchange, String routingKey, Map arguments) throws IOException { - delegate.queueBindNoWait(queue, exchange, routingKey, arguments); - recordQueueBinding(queue, exchange, routingKey, arguments); - } - - public AMQP.Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey) throws IOException { - return queueUnbind(queue, exchange, routingKey, null); - } - - public AMQP.Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey, Map arguments) throws IOException { - deleteRecordedQueueBinding(queue, exchange, routingKey, arguments); - this.maybeDeleteRecordedAutoDeleteExchange(exchange); - return delegate.queueUnbind(queue, exchange, routingKey, arguments); - } - - public AMQP.Queue.PurgeOk queuePurge(String queue) throws IOException { - return delegate.queuePurge(queue); - } - - public GetResponse basicGet(String queue, boolean autoAck) throws IOException { - return delegate.basicGet(queue, autoAck); - } - - public void basicAck(long deliveryTag, boolean multiple) throws IOException { - delegate.basicAck(deliveryTag, multiple); - } - - public void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException { - delegate.basicNack(deliveryTag, multiple, requeue); - } - - public void basicReject(long deliveryTag, boolean requeue) throws IOException { - delegate.basicReject(deliveryTag, requeue); - } - - public String basicConsume(String queue, Consumer callback) throws IOException { - return basicConsume(queue, false, callback); - } - - public String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException { - return basicConsume(queue, autoAck, "", callback); - } - - public String basicConsume(String queue, boolean autoAck, String consumerTag, Consumer callback) throws IOException { - return basicConsume(queue, autoAck, consumerTag, false, false, null, callback); - } - - public String basicConsume(String queue, boolean autoAck, Map arguments, Consumer callback) throws IOException { - return basicConsume(queue, autoAck, "", false, false, arguments, callback); - } - - public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, Consumer callback) throws IOException { - final String result = delegate.basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, callback); - recordConsumer(result, queue, autoAck, exclusive, arguments, callback); - return result; - } - - public void basicCancel(String consumerTag) throws IOException { - RecordedConsumer c = this.deleteRecordedConsumer(consumerTag); - this.maybeDeleteRecordedAutoDeleteQueue(c.getQueue()); - delegate.basicCancel(consumerTag); - } - - public AMQP.Basic.RecoverOk basicRecover() throws IOException { - return delegate.basicRecover(); - } - - public AMQP.Basic.RecoverOk basicRecover(boolean requeue) throws IOException { - return delegate.basicRecover(requeue); - } - - public AMQP.Tx.SelectOk txSelect() throws IOException { - this.usesTransactions = true; - return delegate.txSelect(); - } - - public AMQP.Tx.CommitOk txCommit() throws IOException { - return delegate.txCommit(); - } - - public AMQP.Tx.RollbackOk txRollback() throws IOException { - return delegate.txRollback(); - } - - public AMQP.Confirm.SelectOk confirmSelect() throws IOException { - this.usesPublisherConfirms = true; - return delegate.confirmSelect(); - } - - public long getNextPublishSeqNo() { - return delegate.getNextPublishSeqNo(); - } - - public boolean waitForConfirms() throws InterruptedException { - return delegate.waitForConfirms(); - } - - public boolean waitForConfirms(long timeout) throws InterruptedException, TimeoutException { - return delegate.waitForConfirms(timeout); - } - - public void waitForConfirmsOrDie() throws IOException, InterruptedException { - delegate.waitForConfirmsOrDie(); - } - - public void waitForConfirmsOrDie(long timeout) throws IOException, InterruptedException, TimeoutException { - delegate.waitForConfirmsOrDie(timeout); - } - - public void asyncRpc(Method method) throws IOException { - delegate.asyncRpc(method); - } - - public Command rpc(Method method) throws IOException { - return delegate.rpc(method); - } - - /** - * @see Connection#addShutdownListener(com.rabbitmq.client.ShutdownListener) - */ - public void addShutdownListener(ShutdownListener listener) { - this.shutdownHooks.add(listener); - delegate.addShutdownListener(listener); - } - - public void removeShutdownListener(ShutdownListener listener) { - this.shutdownHooks.remove(listener); - delegate.removeShutdownListener(listener); - } - - public ShutdownSignalException getCloseReason() { - return delegate.getCloseReason(); - } - - public void notifyListeners() { - delegate.notifyListeners(); - } - - public boolean isOpen() { - return delegate.isOpen(); - } - - public void addRecoveryListener(RecoveryListener listener) { - this.recoveryListeners.add(listener); - } - - public void removeRecoveryListener(RecoveryListener listener) { - this.recoveryListeners.remove(listener); - } - - // - // Recovery - // - - public void automaticallyRecover(AutorecoveringConnection connection, Connection connDelegate) throws IOException { - RecoveryAwareChannelN defunctChannel = this.delegate; - this.connection = connection; - this.delegate = (RecoveryAwareChannelN) connDelegate.createChannel(this.getChannelNumber()); - this.delegate.inheritOffsetFrom(defunctChannel); - - this.recoverShutdownListeners(); - this.recoverReturnListeners(); - this.recoverConfirmListeners(); - this.recoverFlowListeners(); - this.recoverState(); - this.notifyRecoveryListeners(); - } - - private void recoverShutdownListeners() { - for (ShutdownListener sh : this.shutdownHooks) { - this.delegate.addShutdownListener(sh); - } - } - - private void recoverReturnListeners() { - for(ReturnListener rl : this.returnListeners) { - this.delegate.addReturnListener(rl); - } - } - - private void recoverConfirmListeners() { - for(ConfirmListener cl : this.confirmListeners) { - this.delegate.addConfirmListener(cl); - } - } - - private void recoverFlowListeners() { - for(FlowListener fl : this.flowListeners) { - this.delegate.addFlowListener(fl); - } - } - - private void recoverState() throws IOException { - if (this.prefetchCountConsumer != 0) { - basicQos(this.prefetchCountConsumer, false); - } - if (this.prefetchCountGlobal != 0) { - basicQos(this.prefetchCountGlobal, true); - } - if(this.usesPublisherConfirms) { - this.confirmSelect(); - } - if(this.usesTransactions) { - this.txSelect(); - } - } - - private void notifyRecoveryListeners() { - for (RecoveryListener f : this.recoveryListeners) { - f.handleRecovery(this); - } - } - - private void recordQueueBinding(String queue, String exchange, String routingKey, Map arguments) { - this.connection.recordQueueBinding(this, queue, exchange, routingKey, arguments); - } - - private boolean deleteRecordedQueueBinding(String queue, String exchange, String routingKey, Map arguments) { - return this.connection.deleteRecordedQueueBinding(this, queue, exchange, routingKey, arguments); - } - - private void recordExchangeBinding(String destination, String source, String routingKey, Map arguments) { - this.connection.recordExchangeBinding(this, destination, source, routingKey, arguments); - } - - private boolean deleteRecordedExchangeBinding(String destination, String source, String routingKey, Map arguments) { - return this.connection.deleteRecordedExchangeBinding(this, destination, source, routingKey, arguments); - } - - private void recordQueue(AMQP.Queue.DeclareOk ok, RecordedQueue q) { - this.connection.recordQueue(ok, q); - } - - private void recordQueue(String queue, RecordedQueue meta) { - this.connection.recordQueue(queue, meta); - } - - private void deleteRecordedQueue(String queue) { - this.connection.deleteRecordedQueue(queue); - } - - private void recordExchange(String exchange, RecordedExchange x) { - this.connection.recordExchange(exchange, x); - } - - private void deleteRecordedExchange(String exchange) { - this.connection.deleteRecordedExchange(exchange); - } - - private void recordConsumer(String result, - String queue, - boolean autoAck, - boolean exclusive, - Map arguments, - Consumer callback) { - RecordedConsumer consumer = new RecordedConsumer(this, queue). - autoAck(autoAck). - consumerTag(result). - exclusive(exclusive). - arguments(arguments). - consumer(callback); - this.connection.recordConsumer(result, consumer); - } - - private RecordedConsumer deleteRecordedConsumer(String consumerTag) { - return this.connection.deleteRecordedConsumer(consumerTag); - } - - private void maybeDeleteRecordedAutoDeleteQueue(String queue) { - this.connection.maybeDeleteRecordedAutoDeleteQueue(queue); - } - - private void maybeDeleteRecordedAutoDeleteExchange(String exchange) { - this.connection.maybeDeleteRecordedAutoDeleteExchange(exchange); - } -} diff --git a/src/com/rabbitmq/client/impl/recovery/AutorecoveringConnection.java b/src/com/rabbitmq/client/impl/recovery/AutorecoveringConnection.java deleted file mode 100644 index 3a73b59b1e..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/AutorecoveringConnection.java +++ /dev/null @@ -1,738 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Address; -import com.rabbitmq.client.BlockedListener; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Recoverable; -import com.rabbitmq.client.RecoveryListener; -import com.rabbitmq.client.ShutdownListener; -import com.rabbitmq.client.ShutdownSignalException; -import com.rabbitmq.client.TopologyRecoveryException; -import com.rabbitmq.client.impl.ConnectionParams; -import com.rabbitmq.client.ExceptionHandler; -import com.rabbitmq.client.impl.FrameHandlerFactory; -import com.rabbitmq.client.impl.NetworkConnection; - -import java.io.IOException; -import java.net.ConnectException; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Connection implementation that performs automatic recovery when - * connection shutdown is not initiated by the application (e.g. due to - * an I/O exception). - * - * Topology (exchanges, queues, bindings, and consumers) can be (and by default is) recovered - * as well, in this order: - * - *
    - *
  1. Exchanges
  2. - *
  3. Queues
  4. - *
  5. Bindings (both queue and exchange-to-exchange)
  6. - *
  7. Consumers
  8. - *
- * - * @see com.rabbitmq.client.Connection - * @see com.rabbitmq.client.Recoverable - * @see com.rabbitmq.client.ConnectionFactory#setAutomaticRecoveryEnabled(boolean) - * @see com.rabbitmq.client.ConnectionFactory#setTopologyRecoveryEnabled(boolean) - * @since 3.3.0 - */ -public class AutorecoveringConnection implements Connection, Recoverable, NetworkConnection { - private final RecoveryAwareAMQConnectionFactory cf; - private final Map channels; - private final ConnectionParams params; - private RecoveryAwareAMQConnection delegate; - - private final List shutdownHooks = new ArrayList(); - private final List recoveryListeners = new ArrayList(); - private final List blockedListeners = new ArrayList(); - - // Records topology changes - private final Map recordedQueues = new ConcurrentHashMap(); - private final List recordedBindings = new ArrayList(); - private final Map recordedExchanges = new ConcurrentHashMap(); - private final Map consumers = new ConcurrentHashMap(); - private final List consumerRecoveryListeners = new ArrayList(); - private final List queueRecoveryListeners = new ArrayList(); - - public AutorecoveringConnection(ConnectionParams params, FrameHandlerFactory f, Address[] addrs) { - this.cf = new RecoveryAwareAMQConnectionFactory(params, f, addrs); - this.params = params; - - this.channels = new ConcurrentHashMap(); - } - - /** - * Private API. - * @throws IOException - * @see com.rabbitmq.client.ConnectionFactory#newConnection(java.util.concurrent.ExecutorService) - */ - public void init() throws IOException { - this.delegate = this.cf.newConnection(); - this.addAutomaticRecoveryListener(); - } - - public void start() throws IOException { - // no-op, AMQConnection#start is executed in ConnectionFactory#newConnection - // and invoking it again will result in a framing error. MK. - } - - /** - * @see com.rabbitmq.client.Connection#createChannel() - */ - public Channel createChannel() throws IOException { - RecoveryAwareChannelN ch = (RecoveryAwareChannelN) delegate.createChannel(); - if (ch == null) { - return null; - } else { - return this.wrapChannel(ch); - } - } - - /** - * @see com.rabbitmq.client.Connection#createChannel(int) - */ - public Channel createChannel(int channelNumber) throws IOException { - return delegate.createChannel(channelNumber); - } - - /** - * Creates a recovering channel from a regular channel and registers it. - * If the regular channel cannot be created (e.g. too many channels are open - * already), returns null. - * - * @param delegateChannel Channel to wrap. - * @return Recovering channel. - */ - private Channel wrapChannel(RecoveryAwareChannelN delegateChannel) { - final AutorecoveringChannel channel = new AutorecoveringChannel(this, delegateChannel); - if (delegateChannel == null) { - return null; - } else { - this.registerChannel(channel); - return channel; - } - } - - void registerChannel(AutorecoveringChannel channel) { - this.channels.put(channel.getChannelNumber(), channel); - } - - void unregisterChannel(AutorecoveringChannel channel) { - this.channels.remove(channel.getChannelNumber()); - } - - /** - * @see com.rabbitmq.client.Connection#getServerProperties() - */ - public Map getServerProperties() { - return delegate.getServerProperties(); - } - - /** - * @see com.rabbitmq.client.Connection#getClientProperties() - */ - public Map getClientProperties() { - return delegate.getClientProperties(); - } - - /** - * @see com.rabbitmq.client.Connection#getFrameMax() - */ - public int getFrameMax() { - return delegate.getFrameMax(); - } - - /** - * @see com.rabbitmq.client.Connection#getHeartbeat() - */ - public int getHeartbeat() { - return delegate.getHeartbeat(); - } - - /** - * @see com.rabbitmq.client.Connection#getChannelMax() - */ - public int getChannelMax() { - return delegate.getChannelMax(); - } - - /** - * @see com.rabbitmq.client.Connection#isOpen() - */ - public boolean isOpen() { - return delegate.isOpen(); - } - - /** - * @see com.rabbitmq.client.Connection#close() - */ - public void close() throws IOException { - delegate.close(); - } - - /** - * @see Connection#close(int) - */ - public void close(int timeout) throws IOException { - delegate.close(timeout); - } - - /** - * @see Connection#close(int, String, int) - */ - public void close(int closeCode, String closeMessage, int timeout) throws IOException { - delegate.close(closeCode, closeMessage, timeout); - } - - /** - * @see com.rabbitmq.client.Connection#abort() - */ - public void abort() { - delegate.abort(); - } - - /** - * @see Connection#abort(int, String, int) - */ - public void abort(int closeCode, String closeMessage, int timeout) { - delegate.abort(closeCode, closeMessage, timeout); - } - - /** - * @see Connection#abort(int, String) - */ - public void abort(int closeCode, String closeMessage) { - delegate.abort(closeCode, closeMessage); - } - - /** - * @see Connection#abort(int) - */ - public void abort(int timeout) { - delegate.abort(timeout); - } - - /** - * @see com.rabbitmq.client.Connection#getCloseReason() - */ - public ShutdownSignalException getCloseReason() { - return delegate.getCloseReason(); - } - - /** - * @see com.rabbitmq.client.ShutdownNotifier#addShutdownListener(com.rabbitmq.client.ShutdownListener) - */ - public void addBlockedListener(BlockedListener listener) { - this.blockedListeners.add(listener); - delegate.addBlockedListener(listener); - } - - /** - * @see Connection#removeBlockedListener(com.rabbitmq.client.BlockedListener) - */ - public boolean removeBlockedListener(BlockedListener listener) { - this.blockedListeners.remove(listener); - return delegate.removeBlockedListener(listener); - } - - /** - * @see com.rabbitmq.client.Connection#clearBlockedListeners() - */ - public void clearBlockedListeners() { - this.blockedListeners.clear(); - delegate.clearBlockedListeners(); - } - - /** - * @see com.rabbitmq.client.Connection#close(int, String) - */ - public void close(int closeCode, String closeMessage) throws IOException { - delegate.close(closeCode, closeMessage); - } - - /** - * @see Connection#addShutdownListener(com.rabbitmq.client.ShutdownListener) - */ - public void addShutdownListener(ShutdownListener listener) { - this.shutdownHooks.add(listener); - delegate.addShutdownListener(listener); - } - - /** - * @see com.rabbitmq.client.ShutdownNotifier#removeShutdownListener(com.rabbitmq.client.ShutdownListener) - */ - public void removeShutdownListener(ShutdownListener listener) { - this.shutdownHooks.remove(listener); - delegate.removeShutdownListener(listener); - } - - /** - * @see com.rabbitmq.client.ShutdownNotifier#notifyListeners() - */ - public void notifyListeners() { - delegate.notifyListeners(); - } - - /** - * Adds the recovery listener - * @param listener {@link com.rabbitmq.client.RecoveryListener} to execute after this connection recovers from network failure - */ - public void addRecoveryListener(RecoveryListener listener) { - this.recoveryListeners.add(listener); - } - - /** - * Removes the recovery listener - * @param listener {@link com.rabbitmq.client.RecoveryListener} to remove - */ - public void removeRecoveryListener(RecoveryListener listener) { - this.recoveryListeners.remove(listener); - } - - /** - * @see com.rabbitmq.client.impl.AMQConnection#getExceptionHandler() - */ - @SuppressWarnings("unused") - public ExceptionHandler getExceptionHandler() { - return this.delegate.getExceptionHandler(); - } - - /** - * @see com.rabbitmq.client.Connection#getPort() - */ - public int getPort() { - return delegate.getPort(); - } - - /** - * @see com.rabbitmq.client.Connection#getAddress() - */ - public InetAddress getAddress() { - return delegate.getAddress(); - } - - /** - * @return client socket address - */ - public InetAddress getLocalAddress() { - return this.delegate.getLocalAddress(); - } - - /** - * @return client socket port - */ - public int getLocalPort() { - return this.delegate.getLocalPort(); - } - - // - // Recovery - // - - private void addAutomaticRecoveryListener() { - final AutorecoveringConnection c = this; - ShutdownListener automaticRecoveryListener = new ShutdownListener() { - public void shutdownCompleted(ShutdownSignalException cause) { - try { - if (!cause.isInitiatedByApplication()) { - c.beginAutomaticRecovery(); - } - } catch (Exception e) { - c.delegate.getExceptionHandler().handleConnectionRecoveryException(c, e); - } - } - }; - synchronized (this) { - if(!this.shutdownHooks.contains(automaticRecoveryListener)) { - this.shutdownHooks.add(automaticRecoveryListener); - } - this.delegate.addShutdownListener(automaticRecoveryListener); - } - } - - /** - * Not part of the public API. Mean to be used by JVM RabbitMQ clients that build on - * top of the Java client and need to be notified when server-named queue name changes - * after recovery. - * - * @param listener listener that observes queue name changes after recovery - */ - public void addQueueRecoveryListener(QueueRecoveryListener listener) { - this.queueRecoveryListeners.add(listener); - } - - /** - * @see com.rabbitmq.client.impl.recovery.AutorecoveringConnection#addQueueRecoveryListener - * @param listener listener to be removed - */ - public void removeQueueRecoveryListener(QueueRecoveryListener listener) { - this.queueRecoveryListeners.remove(listener); - } - - /** - * Not part of the public API. Mean to be used by JVM RabbitMQ clients that build on - * top of the Java client and need to be notified when consumer tag changes - * after recovery. - * - * @param listener listener that observes consumer tag changes after recovery - */ - public void addConsumerRecoveryListener(ConsumerRecoveryListener listener) { - this.consumerRecoveryListeners.add(listener); - } - - /** - * @see com.rabbitmq.client.impl.recovery.AutorecoveringConnection#addConsumerRecoveryListener(ConsumerRecoveryListener) - * @param listener listener to be removed - */ - public void removeConsumerRecoveryListener(ConsumerRecoveryListener listener) { - this.consumerRecoveryListeners.remove(listener); - } - - synchronized private void beginAutomaticRecovery() throws InterruptedException, IOException, TopologyRecoveryException { - Thread.sleep(this.params.getNetworkRecoveryInterval()); - this.recoverConnection(); - this.recoverShutdownListeners(); - this.recoverBlockedListeners(); - this.recoverChannels(); - if(this.params.isTopologyRecoveryEnabled()) { - this.recoverEntities(); - this.recoverConsumers(); - } - - this.notifyRecoveryListeners(); - } - - private void recoverShutdownListeners() { - for (ShutdownListener sh : this.shutdownHooks) { - this.delegate.addShutdownListener(sh); - } - } - - private void recoverBlockedListeners() { - for (BlockedListener bl : this.blockedListeners) { - this.delegate.addBlockedListener(bl); - } - } - - private void recoverConnection() throws IOException, InterruptedException { - boolean recovering = true; - while (recovering) { - try { - this.delegate = this.cf.newConnection(); - recovering = false; - } catch (Exception e) { - // TODO: exponential back-off - Thread.sleep(this.params.getNetworkRecoveryInterval()); - this.getExceptionHandler().handleConnectionRecoveryException(this, e); - } - } - } - - private void recoverChannels() { - for (AutorecoveringChannel ch : this.channels.values()) { - try { - ch.automaticallyRecover(this, this.delegate); - } catch (Throwable t) { - this.delegate.getExceptionHandler().handleChannelRecoveryException(ch, t); - } - } - } - - private void notifyRecoveryListeners() { - for (RecoveryListener f : this.recoveryListeners) { - f.handleRecovery(this); - } - } - - private void recoverEntities() throws TopologyRecoveryException { - // The recovery sequence is the following: - // - // 1. Recover exchanges - // 2. Recover queues - // 3. Recover bindings - // 4. Recover consumers - recoverExchanges(); - recoverQueues(); - recoverBindings(); - } - - private void recoverExchanges() { - // recorded exchanges are guaranteed to be - // non-predefined (we filter out predefined ones - // in exchangeDeclare). MK. - for (RecordedExchange x : this.recordedExchanges.values()) { - try { - x.recover(); - } catch (Exception cause) { - final String message = "Caught an exception while recovering exchange " + x.getName() + - ": " + cause.getMessage(); - TopologyRecoveryException e = new TopologyRecoveryException(message, cause); - this.getExceptionHandler().handleTopologyRecoveryException(delegate, x.getDelegateChannel(), e); - } - } - } - - private void recoverQueues() { - Map copy = new HashMap(this.recordedQueues); - for (Map.Entry entry : copy.entrySet()) { - String oldName = entry.getKey(); - RecordedQueue q = entry.getValue(); - try { - q.recover(); - String newName = q.getName(); - // make sure server-named queues are re-added with - // their new names. MK. - synchronized (this.recordedQueues) { - this.propagateQueueNameChangeToBindings(oldName, newName); - this.propagateQueueNameChangeToConsumers(oldName, newName); - // bug26552: - // remove old name after we've updated the bindings and consumers, - // plus only for server-named queues, both to make sure we don't lose - // anything to recover. MK. - if(q.isServerNamed()) { - deleteRecordedQueue(oldName); - } - this.recordedQueues.put(newName, q); - } - for(QueueRecoveryListener qrl : this.queueRecoveryListeners) { - qrl.queueRecovered(oldName, newName); - } - } catch (Exception cause) { - final String message = "Caught an exception while recovering queue " + oldName + - ": " + cause.getMessage(); - TopologyRecoveryException e = new TopologyRecoveryException(message, cause); - this.getExceptionHandler().handleTopologyRecoveryException(delegate, q.getDelegateChannel(), e); - } - } - } - - private void recoverBindings() { - for (RecordedBinding b : this.recordedBindings) { - try { - b.recover(); - } catch (Exception cause) { - String message = "Caught an exception while recovering binding between " + b.getSource() + - " and " + b.getDestination() + ": " + cause.getMessage(); - TopologyRecoveryException e = new TopologyRecoveryException(message, cause); - this.getExceptionHandler().handleTopologyRecoveryException(delegate, b.getDelegateChannel(), e); - } - } - } - - private void recoverConsumers() { - Map copy = new HashMap(this.consumers); - for (Map.Entry entry : copy.entrySet()) { - String tag = entry.getKey(); - RecordedConsumer consumer = entry.getValue(); - - try { - String newTag = consumer.recover(); - // make sure server-generated tags are re-added. MK. - synchronized (this.consumers) { - this.consumers.remove(tag); - this.consumers.put(newTag, consumer); - } - for(ConsumerRecoveryListener crl : this.consumerRecoveryListeners) { - crl.consumerRecovered(tag, newTag); - } - } catch (Exception cause) { - final String message = "Caught an exception while recovering consumer " + tag + - ": " + cause.getMessage(); - TopologyRecoveryException e = new TopologyRecoveryException(message, cause); - this.getExceptionHandler().handleTopologyRecoveryException(delegate, consumer.getDelegateChannel(), e); - } - } - } - - private void propagateQueueNameChangeToBindings(String oldName, String newName) { - for (RecordedBinding b : this.recordedBindings) { - if (b.getDestination().equals(oldName)) { - b.setDestination(newName); - } - } - } - - private void propagateQueueNameChangeToConsumers(String oldName, String newName) { - for (RecordedConsumer c : this.consumers.values()) { - if (c.getQueue().equals(oldName)) { - c.setQueue(newName); - } - } - } - - synchronized void recordQueueBinding(AutorecoveringChannel ch, - String queue, - String exchange, - String routingKey, - Map arguments) { - RecordedBinding binding = new RecordedQueueBinding(ch). - source(exchange). - destination(queue). - routingKey(routingKey). - arguments(arguments); - if (!this.recordedBindings.contains(binding)) { - this.recordedBindings.add(binding); - } - } - - synchronized boolean deleteRecordedQueueBinding(AutorecoveringChannel ch, - String queue, - String exchange, - String routingKey, - Map arguments) { - RecordedBinding b = new RecordedQueueBinding(ch). - source(exchange). - destination(queue). - routingKey(routingKey). - arguments(arguments); - return this.recordedBindings.remove(b); - } - - synchronized void recordExchangeBinding(AutorecoveringChannel ch, - String destination, - String source, - String routingKey, - Map arguments) { - RecordedBinding binding = new RecordedExchangeBinding(ch). - source(source). - destination(destination). - routingKey(routingKey). - arguments(arguments); - this.recordedBindings.add(binding); - } - - synchronized boolean deleteRecordedExchangeBinding(AutorecoveringChannel ch, - String destination, - String source, - String routingKey, - Map arguments) { - RecordedBinding b = new RecordedExchangeBinding(ch). - source(source). - destination(destination). - routingKey(routingKey). - arguments(arguments); - return this.recordedBindings.remove(b); - } - - void recordQueue(AMQP.Queue.DeclareOk ok, RecordedQueue q) { - this.recordedQueues.put(ok.getQueue(), q); - } - - void recordQueue(String queue, RecordedQueue meta) { - this.recordedQueues.put(queue, meta); - } - - void deleteRecordedQueue(String queue) { - this.recordedQueues.remove(queue); - Set xs = this.removeBindingsWithDestination(queue); - for (RecordedBinding b : xs) { - this.maybeDeleteRecordedAutoDeleteExchange(b.getSource()); - } - } - - void recordExchange(String exchange, RecordedExchange x) { - this.recordedExchanges.put(exchange, x); - } - - void deleteRecordedExchange(String exchange) { - this.recordedExchanges.remove(exchange); - Set xs = this.removeBindingsWithDestination(exchange); - for (RecordedBinding b : xs) { - this.maybeDeleteRecordedAutoDeleteExchange(b.getSource()); - } - } - - void recordConsumer(String result, RecordedConsumer consumer) { - this.consumers.put(result, consumer); - } - - RecordedConsumer deleteRecordedConsumer(String consumerTag) { - return this.consumers.remove(consumerTag); - } - - void maybeDeleteRecordedAutoDeleteQueue(String queue) { - synchronized (this.recordedQueues) { - synchronized (this.consumers) { - if(!hasMoreConsumersOnQueue(this.consumers.values(), queue)) { - RecordedQueue q = this.recordedQueues.get(queue); - // last consumer on this connection is gone, remove recorded queue - // if it is auto-deleted. See bug 26364. - if((q != null) && q.isAutoDelete()) { this.recordedQueues.remove(queue); } - } - } - } - } - - void maybeDeleteRecordedAutoDeleteExchange(String exchange) { - synchronized (this.recordedExchanges) { - synchronized (this.consumers) { - if(!hasMoreDestinationsBoundToExchange(this.recordedBindings, exchange)) { - RecordedExchange x = this.recordedExchanges.get(exchange); - // last binding where this exchange is the source is gone, remove recorded exchange - // if it is auto-deleted. See bug 26364. - if((x != null) && x.isAutoDelete()) { this.recordedExchanges.remove(exchange); } - } - } - } - } - - boolean hasMoreDestinationsBoundToExchange(List bindings, String exchange) { - boolean result = false; - for (RecordedBinding b : bindings) { - if(exchange.equals(b.getSource())) { - result = true; - break; - } - } - return result; - } - - boolean hasMoreConsumersOnQueue(Collection consumers, String queue) { - boolean result = false; - for (RecordedConsumer c : consumers) { - if(queue.equals(c.getQueue())) { - result = true; - break; - } - } - return result; - } - - Set removeBindingsWithDestination(String s) { - Set result = new HashSet(); - for (Iterator it = this.recordedBindings.iterator(); it.hasNext(); ) { - RecordedBinding b = it.next(); - if(b.getDestination().equals(s)) { - it.remove(); - result.add(b); - } - } - return result; - } - - public Map getRecordedQueues() { - return recordedQueues; - } - - public Map getRecordedExchanges() { - return recordedExchanges; - } - - @Override - public String toString() { - return this.delegate.toString(); - } -} diff --git a/src/com/rabbitmq/client/impl/recovery/ConsumerRecoveryListener.java b/src/com/rabbitmq/client/impl/recovery/ConsumerRecoveryListener.java deleted file mode 100644 index b8ca880565..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/ConsumerRecoveryListener.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -/** - * Not part of the public API. Mean to be used by JVM RabbitMQ clients that build on - * top of the Java client and need to be notified when consumer tag changes - * after recovery. - */ -public interface ConsumerRecoveryListener { - void consumerRecovered(String oldConsumerTag, String newConsumerTag); -} diff --git a/src/com/rabbitmq/client/impl/recovery/QueueRecoveryListener.java b/src/com/rabbitmq/client/impl/recovery/QueueRecoveryListener.java deleted file mode 100644 index e9c4ed0d5b..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/QueueRecoveryListener.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -/** - * Not part of the public API. Mean to be used by JVM RabbitMQ clients that build on - * top of the Java client and need to be notified when server-name queue name changes - * after recovery. - */ -public interface QueueRecoveryListener { - void queueRecovered(String oldName, String newName); -} diff --git a/src/com/rabbitmq/client/impl/recovery/RecordedEntity.java b/src/com/rabbitmq/client/impl/recovery/RecordedEntity.java deleted file mode 100644 index 7dae5d1442..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/RecordedEntity.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -import com.rabbitmq.client.Channel; - -/** - * @since 3.3.0 - */ -public class RecordedEntity { - protected final AutorecoveringChannel channel; - - public RecordedEntity(AutorecoveringChannel channel) { - this.channel = channel; - } - - public AutorecoveringChannel getChannel() { - return channel; - } - - public Channel getDelegateChannel() { - return channel.getDelegate(); - } -} diff --git a/src/com/rabbitmq/client/impl/recovery/RecordedExchangeBinding.java b/src/com/rabbitmq/client/impl/recovery/RecordedExchangeBinding.java deleted file mode 100644 index 7dbabcda7b..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/RecordedExchangeBinding.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -import java.io.IOException; - -/** - * @since 3.3.0 - */ -public class RecordedExchangeBinding extends RecordedBinding { - public RecordedExchangeBinding(AutorecoveringChannel channel) { - super(channel); - } - - public void recover() throws IOException { - this.channel.getDelegate().exchangeBind(this.destination, this.source, this.routingKey, this.arguments); - } -} diff --git a/src/com/rabbitmq/client/impl/recovery/RecordedNamedEntity.java b/src/com/rabbitmq/client/impl/recovery/RecordedNamedEntity.java deleted file mode 100644 index 138e5fca43..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/RecordedNamedEntity.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -/** - * @since 3.3.0 - */ -public class RecordedNamedEntity extends RecordedEntity { - protected String name; - - public RecordedNamedEntity(AutorecoveringChannel channel, String name) { - super(channel); - this.name = name; - } - - public String getName() { - return name; - } -} diff --git a/src/com/rabbitmq/client/impl/recovery/RecordedQueue.java b/src/com/rabbitmq/client/impl/recovery/RecordedQueue.java deleted file mode 100644 index 8d4c96b72b..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/RecordedQueue.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -import java.io.IOException; -import java.util.Map; - -/** - * @since 3.3.0 - */ -public class RecordedQueue extends RecordedNamedEntity { - public static final String EMPTY_STRING = ""; - private boolean durable; - private boolean autoDelete; - private Map arguments; - private boolean exclusive; - private boolean serverNamed; - - public RecordedQueue(AutorecoveringChannel channel, String name) { - super(channel, name); - } - - public RecordedQueue exclusive(boolean value) { - this.exclusive = value; - return this; - } - - public RecordedQueue serverNamed(boolean value) { - this.serverNamed = value; - return this; - } - - public boolean isServerNamed() { - return this.serverNamed; - } - - public boolean isAutoDelete() { return this.autoDelete; } - - public void recover() throws IOException { - this.name = this.channel.queueDeclare(this.getNameToUseForRecovery(), - this.durable, - this.exclusive, - this.autoDelete, - this.arguments).getQueue(); - } - - public String getNameToUseForRecovery() { - if(isServerNamed()) { - return EMPTY_STRING; - } else { - return this.name; - } - } - - public RecordedQueue durable(boolean value) { - this.durable = value; - return this; - } - - public RecordedQueue autoDelete(boolean value) { - this.autoDelete = value; - return this; - } - - public RecordedQueue arguments(Map value) { - this.arguments = value; - return this; - } -} diff --git a/src/com/rabbitmq/client/impl/recovery/RecordedQueueBinding.java b/src/com/rabbitmq/client/impl/recovery/RecordedQueueBinding.java deleted file mode 100644 index ac8e4a6161..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/RecordedQueueBinding.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -import java.io.IOException; - -/** - * @since 3.3.0 - */ -public class RecordedQueueBinding extends RecordedBinding { - public RecordedQueueBinding(AutorecoveringChannel channel) { - super(channel); - } - - public void recover() throws IOException { - this.channel.getDelegate().queueBind(this.getDestination(), this.getSource(), this.routingKey, this.arguments); - } -} diff --git a/src/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnection.java b/src/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnection.java deleted file mode 100644 index b4a3475de1..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnection.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -import com.rabbitmq.client.impl.AMQConnection; -import com.rabbitmq.client.impl.ConnectionParams; -import com.rabbitmq.client.impl.FrameHandler; - -import java.util.concurrent.ThreadFactory; - -/** - * {@link com.rabbitmq.client.impl.AMQConnection} modification that uses {@link com.rabbitmq.client.impl.recovery.RecoveryAwareChannelN} - * @since 3.3.0 - */ -public class RecoveryAwareAMQConnection extends AMQConnection { - public RecoveryAwareAMQConnection(ConnectionParams params, FrameHandler handler) { - super(params, handler); - } - - @Override - protected RecoveryAwareChannelManager instantiateChannelManager(int channelMax, ThreadFactory threadFactory) { - return new RecoveryAwareChannelManager(super._workService, channelMax, threadFactory); - } -} diff --git a/src/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnectionFactory.java b/src/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnectionFactory.java deleted file mode 100644 index 58f747842e..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnectionFactory.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -import com.rabbitmq.client.Address; -import com.rabbitmq.client.impl.ConnectionParams; -import com.rabbitmq.client.impl.FrameHandler; -import com.rabbitmq.client.impl.FrameHandlerFactory; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public class RecoveryAwareAMQConnectionFactory { - private final ConnectionParams params; - private final FrameHandlerFactory factory; - private final Address[] addrs; - - public RecoveryAwareAMQConnectionFactory(ConnectionParams params, FrameHandlerFactory factory, Address[] addrs) { - this.params = params; - this.factory = factory; - this.addrs = addrs; - } - - /** - * @return an interface to the connection - * @throws java.io.IOException if it encounters a problem - */ - RecoveryAwareAMQConnection newConnection() throws IOException - { - IOException lastException = null; - for (Address addr : shuffle(addrs)) { - try { - FrameHandler frameHandler = factory.create(addr); - RecoveryAwareAMQConnection conn = new RecoveryAwareAMQConnection(params, frameHandler); - conn.start(); - return conn; - } catch (IOException e) { - lastException = e; - } - } - - throw (lastException != null) ? lastException : new IOException("failed to connect"); - } - - private Address[] shuffle(Address[] addrs) { - List
list = new ArrayList
(Arrays.asList(addrs)); - Collections.shuffle(list); - Address[] result = new Address[addrs.length]; - list.toArray(result); - return result; - } -} diff --git a/src/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelManager.java b/src/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelManager.java deleted file mode 100644 index 2f93c3e8c0..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelManager.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -import com.rabbitmq.client.impl.AMQConnection; -import com.rabbitmq.client.impl.ChannelManager; -import com.rabbitmq.client.impl.ChannelN; -import com.rabbitmq.client.impl.ConsumerWorkService; - -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; - -/** - * @since 3.3.0 - */ -public class RecoveryAwareChannelManager extends ChannelManager { - public RecoveryAwareChannelManager(ConsumerWorkService workService, int channelMax) { - this(workService, channelMax, Executors.defaultThreadFactory()); - } - - public RecoveryAwareChannelManager(ConsumerWorkService workService, int channelMax, ThreadFactory threadFactory) { - super(workService, channelMax, threadFactory); - } - - @Override - protected ChannelN instantiateChannel(AMQConnection connection, int channelNumber, ConsumerWorkService workService) { - return new RecoveryAwareChannelN(connection, channelNumber, workService); - } -} diff --git a/src/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelN.java b/src/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelN.java deleted file mode 100644 index 5b629cce53..0000000000 --- a/src/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelN.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.rabbitmq.client.impl.recovery; - -import com.rabbitmq.client.Command; -import com.rabbitmq.client.impl.AMQConnection; -import com.rabbitmq.client.impl.AMQImpl; -import com.rabbitmq.client.impl.ChannelN; -import com.rabbitmq.client.impl.ConsumerWorkService; - -import java.io.IOException; - -/** - * {@link com.rabbitmq.client.impl.ChannelN} modification that keeps track of delivery - * tags and avoids sending
basic.ack
,
basic.nack
, and
basic.reject
- * for stale tags. - * - * @since 3.3.0 - */ -public class RecoveryAwareChannelN extends ChannelN { - private long maxSeenDeliveryTag = 0; - private long activeDeliveryTagOffset = 0; - - /** - * Construct a new channel on the given connection with the given - * channel number. Usually not called directly - call - * Connection.createChannel instead. - * - * @param connection The connection associated with this channel - * @param channelNumber The channel number to be associated with this channel - * @param workService service for managing this channel's consumer callbacks - */ - public RecoveryAwareChannelN(AMQConnection connection, int channelNumber, ConsumerWorkService workService) { - super(connection, channelNumber, workService); - } - - @Override - protected void processDelivery(Command command, AMQImpl.Basic.Deliver method) { - long tag = method.getDeliveryTag(); - if(tag > maxSeenDeliveryTag) { - maxSeenDeliveryTag = tag; - } - super.processDelivery(command, offsetDeliveryTag(method)); - } - - private AMQImpl.Basic.Deliver offsetDeliveryTag(AMQImpl.Basic.Deliver method) { - return new AMQImpl.Basic.Deliver(method.getConsumerTag(), - method.getDeliveryTag() + activeDeliveryTagOffset, - method.getRedelivered(), - method.getExchange(), - method.getRoutingKey()); - } - - @Override - public void basicAck(long deliveryTag, boolean multiple) throws IOException { - long realTag = deliveryTag - activeDeliveryTagOffset; - if (realTag > 0) { - super.basicAck(realTag, multiple); - } - } - - @Override - public void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException { - long realTag = deliveryTag - activeDeliveryTagOffset; - if (realTag > 0) { - super.basicNack(realTag, multiple, requeue); - } - } - - @Override - public void basicReject(long deliveryTag, boolean requeue) throws IOException { - long realTag = deliveryTag - activeDeliveryTagOffset; - if (realTag > 0) { - super.basicReject(realTag, requeue); - } - } - - void inheritOffsetFrom(RecoveryAwareChannelN other) { - activeDeliveryTagOffset = other.getActiveDeliveryTagOffset() + other.getMaxSeenDeliveryTag(); - maxSeenDeliveryTag = 0; - } - - public long getMaxSeenDeliveryTag() { - return maxSeenDeliveryTag; - } - - public long getActiveDeliveryTagOffset() { - return activeDeliveryTagOffset; - } -} diff --git a/src/com/rabbitmq/client/package.html b/src/com/rabbitmq/client/package.html deleted file mode 100644 index 3a190d8770..0000000000 --- a/src/com/rabbitmq/client/package.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - -The client API proper: classes and interfaces representing the AMQP -connections, channels, and wire-protocol framing descriptors. - - - diff --git a/src/com/rabbitmq/tools/Tracer.java b/src/com/rabbitmq/tools/Tracer.java deleted file mode 100644 index 3cc41a9808..0000000000 --- a/src/com/rabbitmq/tools/Tracer.java +++ /dev/null @@ -1,584 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.tools; - -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.impl.AMQCommand; -import com.rabbitmq.client.impl.AMQContentHeader; -import com.rabbitmq.client.impl.AMQImpl; -import com.rabbitmq.client.impl.Frame; -import com.rabbitmq.utility.BlockingCell; -import com.rabbitmq.utility.Utility; - -/** - * AMQP Protocol Analyzer program. Listens on a port (in-port) and when a - * connection arrives, makes an outbound connection to a host and - * port (out-port). Relays frames from the in-port to the out-port. - * Commands are decoded and printed to a supplied {@link Logger}. - *

- * The stand-alone program ({@link #main(String[])}) prints to System.out, - * using a private {@link AsyncLogger} instance. When the connection closes - * the program listens for a subsequent connection and traces that to the same {@link Logger}. - * This continues until the program is interrupted. - *

- * Options for controlling, for example, whether command bodies are decoded, - * are obtained from System.properties, and are reported to the console - * before starting the trace. - *

- * A {@link Tracer} object may be instantiated, using one of the constructors - *

    - *
  • Tracer(int listenPort, String id, String host, int port, Logger logger, Properties props)
  • - *
  • Tracer(String id)
  • - *
  • Tracer(String id, Properties props) - *

    where the missing parameters default as follows: - *

      - *
    • listenPort defaults to 5673
    • - *
    • host defaults to localhost
    • - *
    • port defaults to 5672
    • - *
    • logger defaults to new AsyncLogger(System.out)
    • and - *
    • props defaults to System.getProperties()
    • - *
    - *
  • - *
- *

- * These constructors block waiting for a connection to arrive on the listenPort. - * Tracing does not begin until the tracer is {@link #start()}ed which {@link Logger#start()}s - * the supplied logger and creates and starts a {@link Thread} for relaying and deconstructing the frames. - *

- * The properties specified in props are used at {@link Tracer#start()} time and may be modified - * before this call. - * @see Tracer.Logger - * @see Tracer.AsyncLogger - */ -public class Tracer implements Runnable { - private static final int DEFAULT_LISTEN_PORT = 5673; - private static final String DEFAULT_CONNECT_HOST = "localhost"; - private static final int DEFAULT_CONNECT_PORT = 5672; - - private static final String PROP_PREFIX = "com.rabbitmq.tools.Tracer."; - - private static boolean getBoolProperty(String propertyName, Properties props) { - return Boolean.parseBoolean(props.getProperty(PROP_PREFIX + propertyName)); - } - - private static void printBoolProperty(String propName, Properties props) { - StringBuilder sb = new StringBuilder(100); - System.out.println(sb.append(PROP_PREFIX).append(propName) - .append(" = ").append(getBoolProperty(propName, props)).toString()); - } - - public static void main(String[] args) { - int listenPort = args.length > 0 ? Integer.parseInt(args[0]) : DEFAULT_LISTEN_PORT; - String connectHost = args.length > 1 ? args[1] : DEFAULT_CONNECT_HOST; - int connectPort = args.length > 2 ? Integer.parseInt(args[2]) : DEFAULT_CONNECT_PORT; - - System.out.println("Usage: Tracer [ [ []]]"); - System.out.println(" Serially traces connections on the , logging\n" - + " frames received and passing them to the connect host and port."); - System.out.println("Invoked as: Tracer " + listenPort + " " + connectHost + " " + connectPort); - - Properties props = System.getProperties(); - printBoolProperty("WITHHOLD_INBOUND_HEARTBEATS", props); - printBoolProperty("WITHHOLD_OUTBOUND_HEARTBEATS", props); - printBoolProperty("NO_ASSEMBLE_FRAMES", props); - printBoolProperty("NO_DECODE_FRAMES", props); - printBoolProperty("SUPPRESS_COMMAND_BODIES", props); - - Logger logger = new AsyncLogger(System.out); // initially stopped - try { - ServerSocket server = new ServerSocket(listenPort); - int counter = 0; - while (true) { - Socket conn = server.accept(); - new Tracer( conn - , "Tracer-" + (counter++) - , connectHost, connectPort - , logger - ).start(); - } - } catch (Exception e) { - logger.stop(); // will stop shared logger thread - e.printStackTrace(); - System.exit(1); - } - } - - private final Properties props; - private final Socket inSock; - private final Socket outSock; - private final String idLabel; - private final DataInputStream iis; - private final DataOutputStream ios; - private final DataInputStream ois; - private final DataOutputStream oos; - private final Logger logger; - private final BlockingCell reportEnd; - private final AtomicBoolean started; - - private Tracer(Socket sock, String id, String host, int port, Logger logger, BlockingCell reportEnd, Properties props) - throws IOException { - - this.props = props; - this.inSock = sock; - this.outSock = new Socket(host, port); - this.idLabel = ": <" + id + "> "; - - this.iis = new DataInputStream(this.inSock.getInputStream()); - this.ios = new DataOutputStream(this.inSock.getOutputStream()); - this.ois = new DataInputStream(this.outSock.getInputStream()); - this.oos = new DataOutputStream(this.outSock.getOutputStream()); - this.logger = logger; - this.reportEnd = reportEnd; - this.started = new AtomicBoolean(false); - } - - private Tracer(Socket sock, String id, String host, int port, Logger logger) - throws IOException { - this(sock, id, host, port, logger, new BlockingCell(), System.getProperties()); - } - - private Tracer(int listenPort, String id, String host, int port, - Logger logger, BlockingCell reportEnd, Properties props) - throws IOException { - this(new ServerSocket(listenPort).accept(), id, host, port, logger, reportEnd, props); - } - - public Tracer(int listenPort, String id, String host, int port, Logger logger, Properties props) throws IOException { - this(listenPort, id, host, port, logger, new BlockingCell(), props); - } - - public Tracer(String id) throws IOException { - this(DEFAULT_LISTEN_PORT, id, DEFAULT_CONNECT_HOST, DEFAULT_CONNECT_PORT, new AsyncLogger(System.out), new BlockingCell(), System.getProperties()); - } - - public Tracer(String id, Properties props) throws IOException { - this(DEFAULT_LISTEN_PORT, id, DEFAULT_CONNECT_HOST, DEFAULT_CONNECT_PORT, new AsyncLogger(System.out), new BlockingCell(), props); - } - - public void start() { - if (this.started.compareAndSet(false, true)) { - this.logger.start(); - new Thread(this).start(); - } - } - - public void run() { - try { - byte[] handshake = new byte[8]; - this.iis.readFully(handshake); - this.oos.write(handshake); - - BlockingCell wio = new BlockingCell(); - - new Thread(new DirectionHandler(wio, true, this.iis, this.oos, this.props)) - .start(); - new Thread(new DirectionHandler(wio, false, this.ois, this.ios, this.props)) - .start(); - - waitAndLogException(wio); // either stops => we stop - } catch (Exception e) { - reportAndLogNonNullException(e); - } finally { - try { this.inSock.close(); } catch (Exception e) { logException(e); } - try { this.outSock.close(); } catch (Exception e) { logException(e); } - this.reportEnd.setIfUnset(null); - this.logger.stop(); - } - } - - private void waitAndLogException(BlockingCell bc) { - reportAndLogNonNullException(bc.uninterruptibleGet()); - } - - private void reportAndLogNonNullException(Exception e) { - if (e!=null) { - this.reportEnd.setIfUnset(e); - logException(e); - } - } - - public void log(String message) { - StringBuilder sb = new StringBuilder(); - this.logger.log(sb.append(System.currentTimeMillis()) - .append(this.idLabel) - .append(message) - .toString()); - } - - public void logException(Exception e) { - log("uncaught " + Utility.makeStackTrace(e)); - } - - private class DirectionHandler implements Runnable { - private final BlockingCell waitCell; - private final boolean silentMode; - private final boolean noDecodeFrames; - private final boolean noAssembleFrames; - private final boolean suppressCommandBodies; - private final boolean writeHeartBeats; - private final String directionIndicator; - private final DataInputStream inStream; - private final DataOutputStream outStream; - private final Map commands; - - public DirectionHandler(BlockingCell waitCell, boolean inBound, - DataInputStream inStream, DataOutputStream outStream, Properties props) { - this.waitCell = waitCell; - this.silentMode = getBoolProperty("SILENT_MODE", props); - this.noDecodeFrames = getBoolProperty("NO_DECODE_FRAMES", props); - this.noAssembleFrames = getBoolProperty("NO_ASSEMBLE_FRAMES", props); - this.suppressCommandBodies = getBoolProperty("SUPPRESS_COMMAND_BODIES", props); - this.writeHeartBeats - = ( inBound && !getBoolProperty("WITHHOLD_INBOUND_HEARTBEATS", props)) - || (!inBound && !getBoolProperty("WITHHOLD_OUTBOUND_HEARTBEATS", props)); - this.directionIndicator = (inBound ? " -> " : " <- "); - this.inStream = inStream; - this.outStream = outStream; - this.commands = new HashMap(); - } - - private Frame readFrame() throws IOException { - return Frame.readFrom(this.inStream); - } - - private void report(int channel, Object object) { - StringBuilder sb = new StringBuilder("ch#").append(channel) - .append(this.directionIndicator).append(object); - Tracer.this.log(sb.toString()); - } - - private void reportFrame(Frame frame) throws IOException { - switch (frame.type) { - - case AMQP.FRAME_METHOD: { - report(frame.channel, AMQImpl.readMethodFrom(frame.getInputStream())); - break; - } - - case AMQP.FRAME_HEADER: { - AMQContentHeader contentHeader = AMQImpl - .readContentHeaderFrom(frame.getInputStream()); - long remainingBodyBytes = contentHeader.getBodySize(); - StringBuilder sb = new StringBuilder("Expected body size: ") - .append(remainingBodyBytes).append("; ") - .append(contentHeader); - report(frame.channel, sb); - break; - } - - default: - report(frame.channel, frame); - } - } - - private void doFrame() throws IOException { - Frame frame = readFrame(); - - if (frame != null) { - if (this.silentMode) { - frame.writeTo(this.outStream); - return; - } - if (frame.type == AMQP.FRAME_HEARTBEAT) { - if (this.writeHeartBeats) { - frame.writeTo(this.outStream); - report(frame.channel, frame); - } else { - report(frame.channel, "(withheld) " + frame.toString()); - } - } else { - frame.writeTo(this.outStream); - if (this.noDecodeFrames) { - report(frame.channel, frame); - } else if (this.noAssembleFrames) { - reportFrame(frame); - } else { - AMQCommand cmd = this.commands.get(frame.channel); - if (cmd == null) { - cmd = new AMQCommand(); - this.commands.put(frame.channel, cmd); - } - if (cmd.handleFrame(frame)) { - report(frame.channel, cmd.toString(this.suppressCommandBodies)); - commands.remove(frame.channel); - } - } - } - } - } - - public void run() { - try { - while (true) { - doFrame(); - } - } catch (Exception e) { - this.waitCell.setIfUnset(e); - } finally { - this.waitCell.setIfUnset(null); - } - } - } - - /** - * Logging strings to an outputStream. Logging may be started and stopped. - */ - public interface Logger { - /** - * Start logging, that is, printing log entries written using - * {@link #log(String)}. Multiple successive starts are equivalent to a - * single start. - * - * @return true if start actually started the logger; - * false otherwise. - */ - boolean start(); - - /** - * Stop logging, that is, stop printing log entries written using - * {@link #log(String)}. Flush preceding writes. The logger can only be - * stopped if started. Multiple successive stops are equivalent to a - * single stop. - * - * @return true if stop actually stopped the logger; - * false otherwise. - */ - boolean stop(); - - /** - * Write msg to the log. This may block, and may block indefinitely if - * the logger is stopped. - * - * @param msg - */ - void log(String msg); - } - - /** - * A {@link Tracer.Logger} designed to print {@link String}s to a designated {@link OutputStream} - * on a private thread. - *

{@link String}s are read from a private queue and printed to a buffered {@link PrintStream} - * which is periodically flushed. - *

- * When instantiated the private queue is created (an in-memory {@link ArrayBlockingQueue} in this - * implementation) and when {@link #start()}ed the private thread is created and started unless it is - * already present. An {@link AsyncLogger} may be started many times, but only one thread is created. - *

- * When {@link #stop()}ed either the number of starts is decremented, or, if this count reaches zero, - * a special element is queued which causes the private thread to end when encountered. - *

- * If the private thread is interrupted, the thread will also end, and the count set to zero, - * This will cause subsequent {@link #stop()}s to be ignored, and the next {@link #start()} will create a new thread. - *

- * {@link #log(String)} never blocks unless the private queue is full; this may never un-block if the {@link Logger} is stopped. - */ - public static class AsyncLogger implements Logger { - private static final int MIN_FLUSH_INTERVAL = 100; - private static final int ONE_SECOND_INTERVAL = 1000; - private static final int LOG_QUEUE_SIZE = 1024 * 1024; - private static final int BUFFER_SIZE = 10 * 1024 * 1024; - - private final Runnable loggerRunnable; - - private final SafeCounter countStarted; - private volatile Thread loggerThread = null; - - /** - * Simple pair class for queue elements. - * @param type of left item - * @param type of right item - */ - private static class Pr { - private final L left; - private final R right; - public L left() { return this.left; } - public R right() { return this.right; } - public Pr(L left, R right) { this.left=left; this.right=right; } - } - - private enum LogCmd { - STOP, - PRINT - } - - private final BlockingQueue > queue = new ArrayBlockingQueue >( - LOG_QUEUE_SIZE, true); - - /** - * Same as {@link #Tracer.AsyncLogger(OutputStream, int)} with a one-second flush interval. - * @param os OutputStream to print to. - */ - public AsyncLogger(OutputStream os) { - this(os, ONE_SECOND_INTERVAL); - } - - /** - * Start/stoppable logger that prints to an {@link OutputStream} with flushes every flushInterval milliseconds. - * @param os OutputStream to print to. - * @param flushInterval in milliseconds, time between flushes. - */ - public AsyncLogger(OutputStream os, int flushInterval) { - if (flushInterval < MIN_FLUSH_INTERVAL) - throw new IllegalArgumentException("Flush interval (" - + flushInterval + "ms) must be positive and at least " - + MIN_FLUSH_INTERVAL + "ms."); - this.countStarted = new SafeCounter(); - - PrintStream printStream = new PrintStream(new BufferedOutputStream( - os, BUFFER_SIZE), false); - this.loggerRunnable = new AsyncLoggerRunnable(printStream, - flushInterval, this.queue); - } - - public void log(String message) { - if (message != null) { - try { - this.queue.put(new Pr(message, LogCmd.PRINT)); - } catch (InterruptedException ie) { - throw new RuntimeException("Interrupted while logging.", ie); - } - } - } - - public boolean start() { - if (this.countStarted.testZeroAndIncrement()) { - this.loggerThread = new Thread(this.loggerRunnable); - this.loggerThread.start(); - return true; - } - return false; // meaning already started - } - - public boolean stop() { - if (this.countStarted.decrementAndTestZero()) { - if (this.loggerThread != null) { - try { - this.queue.put(new Pr(null, LogCmd.STOP)); - } catch (InterruptedException ie) { - this.loggerThread.interrupt(); //try harder - throw new RuntimeException("Interrupted while stopping.", ie); - } - this.loggerThread = null; - } - return true; - } - return false; // meaning already stopped - } - - private class AsyncLoggerRunnable implements Runnable { - private final int flushInterval; - private final PrintStream ps; - private final BlockingQueue > queue; - - public AsyncLoggerRunnable(PrintStream ps, int flushInterval, - BlockingQueue > queue) { - this.flushInterval = flushInterval; - this.ps = ps; - this.queue = queue; - } - - public void run() { - try { - long timeOfNextFlush = System.currentTimeMillis() - + this.flushInterval; - boolean printedSinceLastFlush = false; - while (true) { - long timeToNextFlush; - while (0 >= (timeToNextFlush = timeOfNextFlush - - System.currentTimeMillis())) { - if (printedSinceLastFlush) { - this.ps.flush(); - printedSinceLastFlush = false; - } - timeOfNextFlush += this.flushInterval; - } - Pr item = this.queue.poll(timeToNextFlush, - TimeUnit.MILLISECONDS); - if (item != null) { - if (item.left() != null) { - this.ps.println(item.left()); - printedSinceLastFlush = true; - } - if (item.right() == LogCmd.STOP) break; - } - } - drainCurrentQueue(); - this.ps.println("Stopped."); - this.ps.flush(); - - } catch (InterruptedException ie) { - AsyncLogger.this.countStarted.reset(); - drainCurrentQueue(); - this.ps.println("Interrupted."); - this.ps.flush(); - } - } - - private void drainCurrentQueue() { - int currentSize = this.queue.size(); - while (currentSize-- > 0) { - Pr item = this.queue.poll(); - if (item != null && item.left() != null) - this.ps.println(item.left()); - } - } - } - } - - private static class SafeCounter { - private final Object countMonitor = new Object(); - private int count; - public SafeCounter() { - this.count = 0; - } - public boolean testZeroAndIncrement() { - synchronized (this.countMonitor) { - int val = this.count; - this.count++; - return (val == 0); - } - } - public boolean decrementAndTestZero() { - synchronized (this.countMonitor) { - if (this.count == 0) return false; - --this.count; - return (0 == this.count); - } - } - public void reset() { - synchronized (this.countMonitor) { - this.count = 0; - } - } - } -} diff --git a/src/com/rabbitmq/tools/json/JSONReader.java b/src/com/rabbitmq/tools/json/JSONReader.java deleted file mode 100644 index 57ddcbd3b9..0000000000 --- a/src/com/rabbitmq/tools/json/JSONReader.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - Copyright (c) 2006-2007 Frank Carver - Copyright (c) 2007-2014 GoPivotal, Inc. All Rights Reserved - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -/* - * Based on org.stringtree.json.JSONReader, licensed under APL and - * LGPL. We've chosen APL (see above). The original code was written - * by Frank Carver. Tony Garnock-Jones has made many changes to it - * since then. - */ -package com.rabbitmq.tools.json; - -import java.text.CharacterIterator; -import java.text.StringCharacterIterator; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class JSONReader { - - private static final Object OBJECT_END = new Object(); - private static final Object ARRAY_END = new Object(); - private static final Object COLON = new Object(); - private static final Object COMMA = new Object(); - - private static final Map escapes = new HashMap(); - static { - escapes.put(new Character('"'), new Character('"')); - escapes.put(new Character('\\'), new Character('\\')); - escapes.put(new Character('/'), new Character('/')); - escapes.put(new Character('b'), new Character('\b')); - escapes.put(new Character('f'), new Character('\f')); - escapes.put(new Character('n'), new Character('\n')); - escapes.put(new Character('r'), new Character('\r')); - escapes.put(new Character('t'), new Character('\t')); - } - - private CharacterIterator it; - private char c; - private Object token; - private final StringBuilder buf = new StringBuilder(); - - private char next() { - c = it.next(); - return c; - } - - private void skipWhiteSpace() { - boolean cont; - - do { - cont = true; - if (Character.isWhitespace(c)) { - next(); - } - else if (c == '/' && next() == '/') { - while (c != '\n') { - next(); - } - } - else { - cont = false; - } - } while (cont); - } - - public Object read(String string) { - it = new StringCharacterIterator(string); - c = it.first(); - return read(); - } - - private Object read() { - Object ret = null; - skipWhiteSpace(); - - if (c == '"' || c == '\'') { - char sep = c; - next(); - ret = string(sep); - } else if (c == '[') { - next(); - ret = array(); - } else if (c == ']') { - ret = ARRAY_END; - next(); - } else if (c == ',') { - ret = COMMA; - next(); - } else if (c == '{') { - next(); - ret = object(); - } else if (c == '}') { - ret = OBJECT_END; - next(); - } else if (c == ':') { - ret = COLON; - next(); - } else if (c == 't' && next() == 'r' && next() == 'u' && next() == 'e') { - ret = Boolean.TRUE; - next(); - } else if (c == 'f' && next() == 'a' && next() == 'l' && next() == 's' && next() == 'e') { - ret = Boolean.FALSE; - next(); - } else if (c == 'n' && next() == 'u' && next() == 'l' && next() == 'l') { - next(); - } else if (Character.isDigit(c) || c == '-') { - ret = number(); - } - else { - throw new IllegalStateException("Found invalid token while parsing JSON (around character "+(it.getIndex()-it.getBeginIndex())+"): " + ret); - } - - token = ret; - return ret; - } - - private Object object() { - Map ret = new HashMap(); - String key = (String) read(); // JSON keys must be strings - while (token != OBJECT_END) { - read(); // should be a colon - if (token != OBJECT_END) { - ret.put(key, read()); - if (read() == COMMA) { - key = (String) read(); - } - } - } - - return ret; - } - - private Object array() { - List ret = new ArrayList(); - Object value = read(); - while (token != ARRAY_END) { - ret.add(value); - if (read() == COMMA) { - value = read(); - } - } - return ret; - } - - private Object number() { - buf.setLength(0); - if (c == '-') { - add(); - } - addDigits(); - if (c == '.') { - add(); - addDigits(); - } - if (c == 'e' || c == 'E') { - add(); - if (c == '+' || c == '-') { - add(); - } - addDigits(); - } - - String result = buf.toString(); - try { - return new Integer(result); - } catch (NumberFormatException nfe) { - return new Double(result); - } - } - - /** - * Read a string with a specific delimiter (either ' or ") - */ - private Object string(char sep) { - buf.setLength(0); - while (c != sep) { - if (c == '\\') { - next(); - if (c == 'u') { - add(unicode()); - } else { - Object value = escapes.get(new Character(c)); - if (value != null) { - add(((Character) value).charValue()); - } - // if escaping is invalid, if we're going to ignore the error, - // it makes more sense to put in the literal character instead - // of just skipping it, so we do that - else { - add(); - } - } - } else { - add(); - } - } - next(); - - return buf.toString(); - } - - private void add(char cc) { - buf.append(cc); - next(); - } - - private void add() { - add(c); - } - - private void addDigits() { - while (Character.isDigit(c)) { - add(); - } - } - - private char unicode() { - int value = 0; - for (int i = 0; i < 4; ++i) { - switch (next()) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - value = (value << 4) + c - '0'; - break; - case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': - value = (value << 4) + c - 'a' + 10; - break; - case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': - value = (value << 4) + c - 'A' + 10; - break; - } - } - return (char) value; - } -} diff --git a/src/com/rabbitmq/tools/json/JSONSerializable.java b/src/com/rabbitmq/tools/json/JSONSerializable.java deleted file mode 100644 index ad9f32a304..0000000000 --- a/src/com/rabbitmq/tools/json/JSONSerializable.java +++ /dev/null @@ -1,28 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.tools.json; - -/** - * Interface for classes that wish to control their own serialization. - */ -public interface JSONSerializable { - /** - * Called during serialization to JSON. - */ - void jsonSerialize(JSONWriter w); -} diff --git a/src/com/rabbitmq/tools/json/JSONUtil.java b/src/com/rabbitmq/tools/json/JSONUtil.java deleted file mode 100644 index 7076dd6b5a..0000000000 --- a/src/com/rabbitmq/tools/json/JSONUtil.java +++ /dev/null @@ -1,101 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.tools.json; - -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Map; - -/** - * Utility methods for working with JSON objects in Java. - */ -public class JSONUtil { - /** - * Uses reflection to fill public fields and Bean properties of - * the target object from the source Map. - */ - public static Object fill(Object target, Map source) - throws IntrospectionException, IllegalAccessException, InvocationTargetException - { - return fill(target, source, true); - } - - /** - * Uses reflection to fill public fields and optionally Bean - * properties of the target object from the source Map. - */ - public static Object fill(Object target, Map source, boolean useProperties) - throws IntrospectionException, IllegalAccessException, InvocationTargetException - { - if (useProperties) { - BeanInfo info = Introspector.getBeanInfo(target.getClass()); - - PropertyDescriptor[] props = info.getPropertyDescriptors(); - for (int i = 0; i < props.length; ++i) { - PropertyDescriptor prop = props[i]; - String name = prop.getName(); - Method setter = prop.getWriteMethod(); - if (setter != null && !Modifier.isStatic(setter.getModifiers())) { - //System.out.println(target + " " + name + " <- " + source.get(name)); - setter.invoke(target, source.get(name)); - } - } - } - - Field[] ff = target.getClass().getDeclaredFields(); - for (int i = 0; i < ff.length; ++i) { - Field field = ff[i]; - int fieldMod = field.getModifiers(); - if (Modifier.isPublic(fieldMod) && !(Modifier.isFinal(fieldMod) || - Modifier.isStatic(fieldMod))) - { - //System.out.println(target + " " + field.getName() + " := " + source.get(field.getName())); - try { - field.set(target, source.get(field.getName())); - } catch (IllegalArgumentException iae) { - // no special error processing required - } - } - } - - return target; - } - - /** - * Ignores reflection exceptions while using reflection to fill - * public fields and Bean properties of the target object from the - * source Map. - */ - public static void tryFill(Object target, Map source) { - try { - fill(target, source); - } catch (IntrospectionException ie) { - ie.printStackTrace(); - } catch (IllegalAccessException iae) { - iae.printStackTrace(); - } catch (InvocationTargetException ite) { - ite.printStackTrace(); - } - } -} diff --git a/src/com/rabbitmq/tools/json/JSONWriter.java b/src/com/rabbitmq/tools/json/JSONWriter.java deleted file mode 100644 index 7df0913ac5..0000000000 --- a/src/com/rabbitmq/tools/json/JSONWriter.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - Copyright (c) 2006-2007 Frank Carver - Copyright (c) 2007-2014 GoPivotal, Inc. All Rights Reserved - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -/* - * Based on org.stringtree.json.JSONWriter, licensed under APL and - * LGPL. We've chosen APL (see above). The original code was written - * by Frank Carver. Tony Garnock-Jones has made many changes to it - * since then. - */ -package com.rabbitmq.tools.json; - -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.text.CharacterIterator; -import java.text.StringCharacterIterator; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -public class JSONWriter { - private boolean indentMode = false; - private int indentLevel = 0; - private final StringBuilder buf = new StringBuilder(); - - public JSONWriter() {} - - public JSONWriter(boolean indenting) { - indentMode = indenting; - } - - public boolean getIndentMode() { - return indentMode; - } - - public void setIndentMode(boolean value) { - indentMode = value; - } - - private void newline() { - if (indentMode) { - add('\n'); - for (int i = 0; i < indentLevel; i++) add(' '); - } - } - - public String write(Object object) { - buf.setLength(0); - value(object); - return buf.toString(); - } - - public String write(long n) { - return write(new Long(n)); - } - - public Object write(double d) { - return write(new Double(d)); - } - - public String write(char c) { - return write(new Character(c)); - } - - public String write(boolean b) { - return write(Boolean.valueOf(b)); - } - - @SuppressWarnings("unchecked") - private void value(Object object) { - if (object == null) add("null"); - else if (object instanceof JSONSerializable) { - ((JSONSerializable) object).jsonSerialize(this); - } else if (object instanceof Class) string(object); - else if (object instanceof Boolean) bool(((Boolean) object).booleanValue()); - else if (object instanceof Number) add(object); - else if (object instanceof String) string(object); - else if (object instanceof Character) string(object); - else if (object instanceof Map) map((Map) object); - else if (object.getClass().isArray()) array(object); - else if (object instanceof Collection) array(((Collection) object).iterator()); - else bean(object); - } - - private void bean(Object object) { - writeLimited(object.getClass(), object, null); - } - - /** - * Write only a certain subset of the object's properties and fields. - * @param klass the class to look up properties etc in - * @param object the object - * @param properties explicit list of property/field names to include - may be null for "all" - */ - public void writeLimited(Class klass, Object object, String[] properties) { - Set propertiesSet = null; - if (properties != null) { - propertiesSet = new HashSet(); - for (String p: properties) { - propertiesSet.add(p); - } - } - - add('{'); indentLevel += 2; newline(); - boolean needComma = false; - - BeanInfo info; - try { - info = Introspector.getBeanInfo(klass); - } catch (IntrospectionException ie) { - info = null; - } - - if (info != null) { - PropertyDescriptor[] props = info.getPropertyDescriptors(); - for (int i = 0; i < props.length; ++i) { - PropertyDescriptor prop = props[i]; - String name = prop.getName(); - if (propertiesSet == null && name.equals("class")) { - // We usually don't want the class in there. - continue; - } - if (propertiesSet == null || propertiesSet.contains(name)) { - Method accessor = prop.getReadMethod(); - if (accessor != null && !Modifier.isStatic(accessor.getModifiers())) { - try { - Object value = accessor.invoke(object, (Object[])null); - if (needComma) { add(','); newline(); } - needComma = true; - add(name, value); - } catch (Exception e) { - // Ignore it. - } - } - } - } - } - - Field[] ff = object.getClass().getDeclaredFields(); - for (int i = 0; i < ff.length; ++i) { - Field field = ff[i]; - int fieldMod = field.getModifiers(); - String name = field.getName(); - if (propertiesSet == null || propertiesSet.contains(name)) { - if (!Modifier.isStatic(fieldMod)) { - try { - Object v = field.get(object); - if (needComma) { add(','); newline(); } - needComma = true; - add(name, v); - } catch (Exception e) { - // Ignore it. - } - } - } - } - - indentLevel -= 2; newline(); add('}'); - } - - private void add(String name, Object value) { - add('"'); - add(name); - add("\":"); - value(value); - } - - private void map(Map map) { - add('{'); indentLevel += 2; newline(); - Iterator it = map.keySet().iterator(); - if (it.hasNext()) { - mapEntry(it.next(), map); - } - while (it.hasNext()) { - add(','); newline(); - Object key = it.next(); - value(key); - add(':'); - value(map.get(key)); - } - indentLevel -= 2; newline(); add('}'); - } - private void mapEntry(Object key, Map map) { - value(key); - add(':'); - value(map.get(key)); - } - - private void array(Iterator it) { - add('['); - if (it.hasNext()) value(it.next()); - while (it.hasNext()) { - add(','); - value(it.next()); - } - add(']'); - } - - private void array(Object object) { - add('['); - int length = Array.getLength(object); - if (length > 0) value(Array.get(object, 0)); - for (int i = 1; i < length; ++i) { - add(','); - value(Array.get(object, i)); - } - add(']'); - } - - private void bool(boolean b) { - add(b ? "true" : "false"); - } - - private void string(Object obj) { - add('"'); - CharacterIterator it = new StringCharacterIterator(obj.toString()); - for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) { - if (c == '"') add("\\\""); - else if (c == '\\') add("\\\\"); - else if (c == '/') add("\\/"); - else if (c == '\b') add("\\b"); - else if (c == '\f') add("\\f"); - else if (c == '\n') add("\\n"); - else if (c == '\r') add("\\r"); - else if (c == '\t') add("\\t"); - else if (Character.isISOControl(c)) { - unicode(c); - } else { - add(c); - } - } - add('"'); - } - - private void add(Object obj) { - buf.append(obj); - } - - private void add(char c) { - buf.append(c); - } - - static final char[] hex = "0123456789ABCDEF".toCharArray(); - - private void unicode(char c) { - add("\\u"); - int n = c; - for (int i = 0; i < 4; ++i) { - int digit = (n & 0xf000) >> 12; - add(hex[digit]); - n <<= 4; - } - } -} diff --git a/src/com/rabbitmq/tools/json/package.html b/src/com/rabbitmq/tools/json/package.html deleted file mode 100644 index 625fed317f..0000000000 --- a/src/com/rabbitmq/tools/json/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - -JSON reader/writer and utility classes. - - - diff --git a/src/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java b/src/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java deleted file mode 100644 index de8ba54ff0..0000000000 --- a/src/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java +++ /dev/null @@ -1,227 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.tools.jsonrpc; - -import java.io.IOException; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeoutException; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.RpcClient; -import com.rabbitmq.client.ShutdownSignalException; -import com.rabbitmq.tools.json.JSONReader; -import com.rabbitmq.tools.json.JSONWriter; - -/** - JSON-RPC is a lightweight - RPC mechanism using JSON - as a data language for request and reply messages. It is - rapidly becoming a standard in web development, where it is - used to make RPC requests over HTTP. RabbitMQ provides an - AMQP transport binding for JSON-RPC in the form of the - JsonRpcClient class. - - JSON-RPC services are self-describing - each service is able - to list its supported procedures, and each procedure - describes its parameters and types. An instance of - JsonRpcClient retrieves its service description using the - standard system.describe procedure when it is - constructed, and uses the information to coerce parameter - types appropriately. A JSON service description is parsed - into instances of ServiceDescription. Client - code can access the service description by reading the - serviceDescription field of - JsonRpcClient instances. - - @see #call(String, Object[]) - @see #call(String[]) - */ -public class JsonRpcClient extends RpcClient implements InvocationHandler { - /** Holds the JSON-RPC service description for this client. */ - private ServiceDescription serviceDescription; - - /** - * Construct a new JsonRpcClient, passing the parameters through - * to RpcClient's constructor. The service description record is - * retrieved from the server during construction. - * @throws TimeoutException if a response is not received within the timeout specified, if any - */ - public JsonRpcClient(Channel channel, String exchange, String routingKey, int timeout) - throws IOException, JsonRpcException, TimeoutException - { - super(channel, exchange, routingKey, timeout); - retrieveServiceDescription(); - } - - public JsonRpcClient(Channel channel, String exchange, String routingKey) - throws IOException, JsonRpcException, TimeoutException - { - this(channel, exchange, routingKey, RpcClient.NO_TIMEOUT); - } - - /** - * Private API - parses a JSON-RPC reply object, checking it for exceptions. - * @return the result contained within the reply, if no exception is found - * Throws JsonRpcException if the reply object contained an exception - */ - public static Object checkReply(Map reply) - throws JsonRpcException - { - if (reply.containsKey("error")) { - @SuppressWarnings("unchecked") - Map map = (Map) reply.get("error"); - // actually a Map - throw new JsonRpcException(map); - } - - Object result = reply.get("result"); - //System.out.println(new JSONWriter().write(result)); - return result; - } - - /** - * Public API - builds, encodes and sends a JSON-RPC request, and - * waits for the response. - * @return the result contained within the reply, if no exception is found - * @throws JsonRpcException if the reply object contained an exception - * @throws TimeoutException if a response is not received within the timeout specified, if any - */ - public Object call(String method, Object[] params) throws IOException, JsonRpcException, TimeoutException - { - HashMap request = new HashMap(); - request.put("id", null); - request.put("method", method); - request.put("version", ServiceDescription.JSON_RPC_VERSION); - request.put("params", (params == null) ? new Object[0] : params); - String requestStr = new JSONWriter().write(request); - try { - String replyStr = this.stringCall(requestStr); - @SuppressWarnings("unchecked") - Map map = (Map) (new JSONReader().read(replyStr)); - return checkReply(map); - } catch(ShutdownSignalException ex) { - throw new IOException(ex.getMessage()); // wrap, re-throw - } - - } - - /** - * Public API - implements InvocationHandler.invoke. This is - * useful for constructing dynamic proxies for JSON-RPC - * interfaces. - */ - public Object invoke(Object proxy, Method method, Object[] args) - throws Throwable - { - return call(method.getName(), args); - } - - /** - * Public API - gets a dynamic proxy for a particular interface class. - */ - public Object createProxy(Class klass) - throws IllegalArgumentException - { - return Proxy.newProxyInstance(klass.getClassLoader(), - new Class[] { klass }, - this); - } - - /** - * Private API - used by {@link #call(String[])} to ad-hoc convert - * strings into the required data types for a call. - */ - public static Object coerce(String val, String type) - throws NumberFormatException - { - if ("bit".equals(type)) { - return Boolean.getBoolean(val) ? Boolean.TRUE : Boolean.FALSE; - } else if ("num".equals(type)) { - try { - return new Integer(val); - } catch (NumberFormatException nfe) { - return new Double(val); - } - } else if ("str".equals(type)) { - return val; - } else if ("arr".equals(type) || "obj".equals(type) || "any".equals(type)) { - return new JSONReader().read(val); - } else if ("nil".equals(type)) { - return null; - } else { - throw new IllegalArgumentException("Bad type: " + type); - } - } - - /** - * Public API - as {@link #call(String,Object[])}, but takes the - * method name from the first entry in args, and the - * parameters from subsequent entries. All parameter values are - * passed through coerce() to attempt to make them the types the - * server is expecting. - * @return the result contained within the reply, if no exception is found - * @throws JsonRpcException if the reply object contained an exception - * @throws NumberFormatException if a coercion failed - * @throws TimeoutException if a response is not received within the timeout specified, if any - * @see #coerce - */ - public Object call(String[] args) - throws NumberFormatException, IOException, JsonRpcException, TimeoutException - { - if (args.length == 0) { - throw new IllegalArgumentException("First string argument must be method name"); - } - - String method = args[0]; - int arity = args.length - 1; - ProcedureDescription proc = serviceDescription.getProcedure(method, arity); - ParameterDescription[] params = proc.getParams(); - - Object[] actuals = new Object[arity]; - for (int count = 0; count < params.length; count++) { - actuals[count] = coerce(args[count + 1], params[count].type); - } - - return call(method, actuals); - } - - /** - * Public API - gets the service description record that this - * service loaded from the server itself at construction time. - */ - public ServiceDescription getServiceDescription() { - return serviceDescription; - } - - /** - * Private API - invokes the "system.describe" method on the - * server, and parses and stores the resulting service description - * in this object. - * TODO: Avoid calling this from the constructor. - * @throws TimeoutException if a response is not received within the timeout specified, if any - */ - private void retrieveServiceDescription() throws IOException, JsonRpcException, TimeoutException - { - @SuppressWarnings("unchecked") - Map rawServiceDescription = (Map) call("system.describe", null); - serviceDescription = new ServiceDescription(rawServiceDescription); - } -} diff --git a/src/com/rabbitmq/tools/jsonrpc/JsonRpcException.java b/src/com/rabbitmq/tools/jsonrpc/JsonRpcException.java deleted file mode 100644 index 0da710896c..0000000000 --- a/src/com/rabbitmq/tools/jsonrpc/JsonRpcException.java +++ /dev/null @@ -1,53 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.tools.jsonrpc; - -import java.util.Map; - -import com.rabbitmq.tools.json.JSONWriter; - -/** - * Thrown when a JSON-RPC service indicates an error occurred during a call. - */ -public class JsonRpcException extends Exception { - /** - * Default serialized version ID - */ - private static final long serialVersionUID = 1L; - /** Usually the constant string, "JSONRPCError" */ - public String name; - /** Error code */ - public int code; - /** Error message */ - public String message; - /** Error detail object - may not always be present or meaningful */ - public Object error; - - public JsonRpcException() { - // no work needed in default no-arg constructor - } - - public JsonRpcException(Map errorMap) { - super(new JSONWriter().write(errorMap)); - name = (String) errorMap.get("name"); - code = 0; - if (errorMap.get("code") != null) { code = ((Integer) errorMap.get("code")); } - message = (String) errorMap.get("message"); - error = errorMap.get("error"); - } -} diff --git a/src/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java b/src/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java deleted file mode 100644 index ac0d6a2d99..0000000000 --- a/src/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java +++ /dev/null @@ -1,208 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.tools.jsonrpc; - -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.StringRpcServer; -import com.rabbitmq.tools.json.JSONReader; -import com.rabbitmq.tools.json.JSONWriter; - -/** - * JSON-RPC Server class. - * - * Given a Java {@link Class}, representing an interface, and an - * implementation of that interface, JsonRpcServer will reflect on the - * class to construct the {@link ServiceDescription}, and will route - * incoming requests for methods on the interface to the - * implementation object while the mainloop() is running. - * - * @see com.rabbitmq.client.RpcServer - * @see JsonRpcClient - */ -public class JsonRpcServer extends StringRpcServer { - /** Holds the JSON-RPC service description for this client. */ - public ServiceDescription serviceDescription; - /** The interface this server implements. */ - public Class interfaceClass; - /** The instance backing this server. */ - public Object interfaceInstance; - - /** - * Construct a server that talks to the outside world using the - * given channel, and constructs a fresh temporary - * queue. Use getQueueName() to discover the created queue name. - * @param channel AMQP channel to use - * @param interfaceClass Java interface that this server is exposing to the world - * @param interfaceInstance Java instance (of interfaceClass) that is being exposed - * @throws IOException if something goes wrong during an AMQP operation - */ - public JsonRpcServer(Channel channel, - Class interfaceClass, - Object interfaceInstance) - throws IOException - { - super(channel); - init(interfaceClass, interfaceInstance); - } - - private void init(Class interfaceClass, Object interfaceInstance) - { - this.interfaceClass = interfaceClass; - this.interfaceInstance = interfaceInstance; - this.serviceDescription = new ServiceDescription(interfaceClass); - } - - /** - * Construct a server that talks to the outside world using the - * given channel and queue name. Our superclass, - * RpcServer, expects the queue to exist at the time of - * construction. - * @param channel AMQP channel to use - * @param queueName AMQP queue name to listen for requests on - * @param interfaceClass Java interface that this server is exposing to the world - * @param interfaceInstance Java instance (of interfaceClass) that is being exposed - * @throws IOException if something goes wrong during an AMQP operation - */ - public JsonRpcServer(Channel channel, - String queueName, - Class interfaceClass, - Object interfaceInstance) - throws IOException - { - super(channel, queueName); - init(interfaceClass, interfaceInstance); - } - - /** - * Override our superclass' method, dispatching to doCall. - */ - public String handleStringCall(String requestBody, AMQP.BasicProperties replyProperties) - { - String replyBody = doCall(requestBody); - return replyBody; - } - - /** - * Runs a single JSON-RPC request. - * @param requestBody the JSON-RPC request string (a JSON encoded value) - * @return a JSON-RPC response string (a JSON encoded value) - */ - public String doCall(String requestBody) - { - Object id; - String method; - Object[] params; - try { - @SuppressWarnings("unchecked") - Map request = (Map) new JSONReader().read(requestBody); - if (request == null) { - return errorResponse(null, 400, "Bad Request", null); - } - if (!ServiceDescription.JSON_RPC_VERSION.equals(request.get("version"))) { - return errorResponse(null, 505, "JSONRPC version not supported", null); - } - - id = request.get("id"); - method = (String) request.get("method"); - List parmList = (List) request.get("params"); - params = parmList.toArray(); - } catch (ClassCastException cce) { - // Bogus request! - return errorResponse(null, 400, "Bad Request", null); - } - - if (method.equals("system.describe")) { - return resultResponse(id, serviceDescription); - } else if (method.startsWith("system.")) { - return errorResponse(id, 403, "System methods forbidden", null); - } else { - Object result; - try { - result = matchingMethod(method, params).invoke(interfaceInstance, params); - } catch (Throwable t) { - return errorResponse(id, 500, "Internal Server Error", t); - } - return resultResponse(id, result); - } - } - - /** - * Retrieves the best matching method for the given method name and parameters. - * - * Subclasses may override this if they have specialised - * dispatching requirements, so long as they continue to honour - * their ServiceDescription. - */ - public Method matchingMethod(String methodName, Object[] params) - { - ProcedureDescription proc = serviceDescription.getProcedure(methodName, params.length); - return proc.internal_getMethod(); - } - - /** - * Construct and encode a JSON-RPC error response for the request - * ID given, using the code, message, and possible - * (JSON-encodable) argument passed in. - */ - public static String errorResponse(Object id, int code, String message, Object errorArg) { - Map err = new HashMap(); - err.put("name", "JSONRPCError"); - err.put("code", code); - err.put("message", message); - err.put("error", errorArg); - return response(id, "error", err); - } - - /** - * Construct and encode a JSON-RPC success response for the - * request ID given, using the result value passed in. - */ - public static String resultResponse(Object id, Object result) { - return response(id, "result", result); - } - - /** - * Private API - used by errorResponse and resultResponse. - */ - public static String response(Object id, String label, Object value) { - Map resp = new HashMap(); - resp.put("version", ServiceDescription.JSON_RPC_VERSION); - if (id != null) { - resp.put("id", id); - } - resp.put(label, value); - String respStr = new JSONWriter().write(resp); - //System.err.println(respStr); - return respStr; - } - - /** - * Public API - gets the service description record that this - * service built from interfaceClass at construction time. - */ - public ServiceDescription getServiceDescription() { - return serviceDescription; - } -} diff --git a/src/com/rabbitmq/tools/jsonrpc/ProcedureDescription.java b/src/com/rabbitmq/tools/jsonrpc/ProcedureDescription.java deleted file mode 100644 index 842396397a..0000000000 --- a/src/com/rabbitmq/tools/jsonrpc/ProcedureDescription.java +++ /dev/null @@ -1,99 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.tools.jsonrpc; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.Map; - -import com.rabbitmq.tools.json.JSONUtil; - -/** - * Description of a single JSON-RPC procedure. - */ -public class ProcedureDescription { - /** Procedure name */ - public String name; - /** Human-readable procedure summary */ - public String summary; - /** Human-readable instructions for how to get information on the procedure's operation */ - public String help; - /** True if this procedure is idempotent, that is, can be accessed via HTTP GET */ - public boolean idempotent; - - /** Descriptions of parameters for this procedure */ - private ParameterDescription[] params; - /** Return type for this procedure */ - private String returnType; - - /** Reflected method object, used for service invocation */ - private Method method; - - public ProcedureDescription(Map pm) { - JSONUtil.tryFill(this, pm); - - @SuppressWarnings("unchecked") - List> p = (List>) pm.get("params"); - params = new ParameterDescription[p.size()]; - int count = 0; - for (Map param_map: p) { - ParameterDescription param = new ParameterDescription(param_map); - params[count++] = param; - } - } - - public ProcedureDescription(Method m) { - this.method = m; - this.name = m.getName(); - this.summary = ""; - this.help = ""; - this.idempotent = false; - Class[] parameterTypes = m.getParameterTypes(); - this.params = new ParameterDescription[parameterTypes.length]; - for (int i = 0; i < parameterTypes.length; i++) { - params[i] = new ParameterDescription(i, parameterTypes[i]); - } - this.returnType = ParameterDescription.lookup(m.getReturnType()); - } - - public ProcedureDescription() { - // no work to do here - } - - /** Getter for return type */ - public String getReturn() { return returnType; } - /** Private API - used via reflection during parsing/loading */ - public void setReturn(String value) { returnType = value; } - - /** Private API - used to get the reflected method object, for servers */ - public Method internal_getMethod() { return method; } - - /** Gets an array of parameter descriptions for all this procedure's parameters */ - public ParameterDescription[] internal_getParams() { - return params; - } - - /** Retrieves the parameter count for this procedure */ - public int arity() { - return (params == null) ? 0 : params.length; - } - - public ParameterDescription[] getParams() { - return params; - } -} diff --git a/src/com/rabbitmq/tools/jsonrpc/package.html b/src/com/rabbitmq/tools/jsonrpc/package.html deleted file mode 100644 index 04a156cced..0000000000 --- a/src/com/rabbitmq/tools/jsonrpc/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - -JSON-RPC client and server classes for supporting JSON-RPC over an AMQP transport. - - - diff --git a/src/com/rabbitmq/tools/package.html b/src/com/rabbitmq/tools/package.html deleted file mode 100644 index d9972631c5..0000000000 --- a/src/com/rabbitmq/tools/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - -Non-core utilities and administration tools. - - - diff --git a/src/com/rabbitmq/utility/BlockingValueOrException.java b/src/com/rabbitmq/utility/BlockingValueOrException.java deleted file mode 100644 index 12d299bc83..0000000000 --- a/src/com/rabbitmq/utility/BlockingValueOrException.java +++ /dev/null @@ -1,39 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.utility; - -import java.util.concurrent.TimeoutException; - -public class BlockingValueOrException> - extends BlockingCell> -{ - public void setValue(V v) { - super.set(ValueOrException.makeValue(v)); - } - - public void setException(E e) { - super.set(ValueOrException.makeException(e)); - } - - public V uninterruptibleGetValue() throws E { - return uninterruptibleGet().getValue(); - } - - public V uninterruptibleGetValue(int timeout) throws E, TimeoutException { - return uninterruptibleGet(timeout).getValue(); - } -} diff --git a/src/com/rabbitmq/utility/SensibleClone.java b/src/com/rabbitmq/utility/SensibleClone.java deleted file mode 100644 index a21868b0de..0000000000 --- a/src/com/rabbitmq/utility/SensibleClone.java +++ /dev/null @@ -1,32 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.utility; - -/** - * This interface exists as a workaround for the annoyingness of java.lang.Cloneable. - * It is used for generic methods which need to accept something they can actually clone - * (Object.clone is protected and java.lang.Cloneable does not define a public clone method) - * and want to provide some guarantees of the type of the cloned object. - */ -public interface SensibleClone> extends Cloneable { - - /** - * Like Object.clone but sensible; in particular, public and declared to return - * the right type. - */ - public T sensibleClone(); -} diff --git a/src/com/rabbitmq/utility/SingleShotLinearTimer.java b/src/com/rabbitmq/utility/SingleShotLinearTimer.java deleted file mode 100644 index 71f130f21b..0000000000 --- a/src/com/rabbitmq/utility/SingleShotLinearTimer.java +++ /dev/null @@ -1,101 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.utility; - -import com.rabbitmq.client.impl.AMQChannel; - -/** - * This class provides a very stripped-down clone of some of the functionality in - * java.util.Timer (notably Timer.schedule(TimerTask task, long delay) but - * uses System.nanoTime() rather than System.currentTimeMillis() as a measure - * of the underlying time, and thus behaves correctly if the system clock jumps - * around. - * - * This class does not have any relation to TimerTask due to the coupling - * between TimerTask and Timer - for example if someone invokes - * TimerTask.cancel(), we can't find out about it as TimerTask.state is - * package-private. - * - * We currently just use this to time the quiescing RPC in AMQChannel. - * - * @see AMQChannel - */ - -public class SingleShotLinearTimer { - private volatile Runnable _task; - private Thread _thread; - - public synchronized void schedule(Runnable task, int timeoutMillisec) { - if (task == null) { - throw new IllegalArgumentException("Don't schedule a null task"); - } - - if (_task != null) { - throw new UnsupportedOperationException("Don't schedule more than one task"); - } - - if (timeoutMillisec < 0) { - throw new IllegalArgumentException("Timeout must not be negative"); - } - - _task = task; - - _thread = new Thread(new TimerThread(timeoutMillisec)); - _thread.setDaemon(true); - _thread.start(); - } - - private static final long NANOS_IN_MILLI = 1000 * 1000; - - private class TimerThread implements Runnable { - private final long _runTime; - - public TimerThread(long timeoutMillisec) { - _runTime = System.nanoTime() / NANOS_IN_MILLI + timeoutMillisec; - } - - public void run() { - try { - long now; - while ((now = System.nanoTime() / NANOS_IN_MILLI) < _runTime) { - if (_task == null) break; - - try { - synchronized(this) { - wait(_runTime - now); - } - } catch (InterruptedException e) { - // Don't care - } - } - - Runnable task = _task; - if (task != null) { - task.run(); - } - - } finally { - _task = null; - } - } - } - - public void cancel() { - _task = null; - } -} diff --git a/src/com/rabbitmq/utility/Utility.java b/src/com/rabbitmq/utility/Utility.java deleted file mode 100644 index c85dcdd081..0000000000 --- a/src/com/rabbitmq/utility/Utility.java +++ /dev/null @@ -1,83 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.utility; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; - -/** - * Catch-all holder class for static helper methods. - */ - -public class Utility { - static class ThrowableCreatedElsewhere extends Throwable { - /** Default for non-checking. */ - private static final long serialVersionUID = 1L; - - public ThrowableCreatedElsewhere(Throwable throwable) { - super(throwable.getClass() + " created elsewhere"); - this.setStackTrace(throwable.getStackTrace()); - } - - @Override public Throwable fillInStackTrace(){ - return this; - } - } - - public static > T fixStackTrace(T throwable) { - throwable = throwable.sensibleClone(); - - if(throwable.getCause() == null) { - // We'd like to preserve the original stack trace in the cause. - // Unfortunately Java doesn't let you set the cause once it's been - // set once. This means we have to choose between either - // - not preserving the type - // - sometimes losing the original stack trace - // - performing nasty reflective voodoo which may or may not work - // We only lose the original stack trace when there's a root cause - // which will hopefully be enlightening enough on its own that it - // doesn't matter too much. - try { - throwable.initCause(new ThrowableCreatedElsewhere(throwable)); - } catch(IllegalStateException e) { - // This exception was explicitly initialised with a null cause. - // Alas this means we can't set the cause even though it has none. - // Thanks. - } - } - - - throwable.fillInStackTrace(); - // We want to remove fixStackTrace from the trace. - StackTraceElement[] existing = throwable.getStackTrace(); - StackTraceElement[] newTrace = new StackTraceElement[existing.length - 1]; - System.arraycopy(existing, 1, newTrace, 0, newTrace.length); - throwable.setStackTrace(newTrace); - return throwable; - } - - - public static String makeStackTrace(Throwable throwable) { - ByteArrayOutputStream baOutStream = new ByteArrayOutputStream(); - PrintStream printStream = new PrintStream(baOutStream, false); - throwable.printStackTrace(printStream); - printStream.flush(); // since we don't automatically do so - String text = baOutStream.toString(); - printStream.close(); // closes baOutStream - return text; - } -} diff --git a/src/com/rabbitmq/utility/package.html b/src/com/rabbitmq/utility/package.html deleted file mode 100644 index 6a0ca1e0d0..0000000000 --- a/src/com/rabbitmq/utility/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - -Utility package of helper classes, mostly used in the implementation code. - - - diff --git a/src/main/java/com/rabbitmq/client/Address.java b/src/main/java/com/rabbitmq/client/Address.java new file mode 100644 index 0000000000..0ed96643ea --- /dev/null +++ b/src/main/java/com/rabbitmq/client/Address.java @@ -0,0 +1,202 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import java.net.InetSocketAddress; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A representation of network addresses, i.e. host/port pairs, + * with some utility functions for parsing address strings. +*/ +public class Address { + private static final Logger LOGGER = LoggerFactory.getLogger(Address.class); + + /** + * host name + **/ + private final String _host; + /** + * port number + **/ + private final int _port; + + /** + * Construct an address from a host name and port number. + * + * @param host the host name + * @param port the port number + */ + public Address(String host, int port) { + _host = host; + _port = port; + } + + /** + * Construct an address from a host. + * + * @param host the host name + */ + public Address(String host) { + _host = host; + _port = ConnectionFactory.USE_DEFAULT_PORT; + } + + /** + * Get the host name + * + * @return the host name + */ + public String getHost() { + return _host; + } + + /** + * Get the port number + * + * @return the port number + */ + public int getPort() { + return _port; + } + + /** + * Extracts hostname or IP address from a string containing a hostname, IP address, + * hostname:port pair or IP address:port pair. + * Note that IPv6 addresses must be quoted with square brackets, e.g. [2001:db8:85a3:8d3:1319:8a2e:370:7348]. + * + * @param addressString the string to extract hostname from + * @return the hostname or IP address + */ + public static String parseHost(String addressString) { + // we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671 + int lastColon = addressString.lastIndexOf(":"); + int lastClosingSquareBracket = addressString.lastIndexOf("]"); + if (lastClosingSquareBracket == -1) { + String[] parts = addressString.split(":"); + if (parts.length > 2) { + String msg = "Address " + + addressString + + " seems to contain an unquoted IPv6 address. Make sure you quote IPv6 addresses like so: [2001:db8:85a3:8d3:1319:8a2e:370:7348]"; + LOGGER.error(msg); + throw new IllegalArgumentException(msg); + } + + return parts[0]; + } + + if (lastClosingSquareBracket < lastColon) { + // there is a port + return addressString.substring(0, lastColon); + } else { + return addressString; + } + } + + public static int parsePort(String addressString) { + // we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671 + int lastColon = addressString.lastIndexOf(":"); + int lastClosingSquareBracket = addressString.lastIndexOf("]"); + if (lastClosingSquareBracket == -1) { + String[] parts = addressString.split(":"); + if (parts.length > 2) { + String msg = "Address " + + addressString + + " seems to contain an unquoted IPv6 address. Make sure you quote IPv6 addresses like so: [2001:db8:85a3:8d3:1319:8a2e:370:7348]"; + LOGGER.error(msg); + throw new IllegalArgumentException(msg); + } + + if (parts.length == 2) { + return Integer.parseInt(parts[1]); + } + + return ConnectionFactory.USE_DEFAULT_PORT; + } + + if (lastClosingSquareBracket < lastColon) { + // there is a port + return Integer.parseInt(addressString.substring(lastColon + 1)); + } + + return ConnectionFactory.USE_DEFAULT_PORT; + } + + public static boolean isHostWithPort(String addressString) { + // we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671 + int lastColon = addressString.lastIndexOf(":"); + int lastClosingSquareBracket = addressString.lastIndexOf("]"); + + if (lastClosingSquareBracket == -1) { + return addressString.contains(":"); + } else { + return lastClosingSquareBracket < lastColon; + } + } + + /** + * Factory method: takes a formatted addressString string as construction parameter + * @param addressString an addressString of the form "host[:port]". + * @return an {@link Address} from the given data + */ + public static Address parseAddress(String addressString) { + if (isHostWithPort(addressString)) { + return new Address(parseHost(addressString), parsePort(addressString)); + } else { + return new Address(addressString); + } + } + + /** + * Construct an InetSocketAddress for this address with a specific port + */ + public InetSocketAddress toInetSocketAddress(int port) { + return new InetSocketAddress(getHost(), port); + } + + /** + * Array-based factory method: takes an array of formatted address strings as construction parameter + * @param addresses array of strings of form "host[:port],..." + * @return a list of {@link Address} values + */ + public static Address[] parseAddresses(String addresses) { + String[] addrs = addresses.split(" *, *"); + Address[] res = new Address[addrs.length]; + for (int i = 0; i < addrs.length; i++) { + res[i] = Address.parseAddress(addrs[i]); + } + return res; + } + + @Override public int hashCode() { + return 31 * _host.hashCode() + _port; + } + + @Override public boolean equals(Object obj) { + if(this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + final Address addr = (Address)obj; + return _host.equals(addr._host) && _port == addr._port; + } + + @Override public String toString() { + return _port == -1 ? _host : _host + ":" + _port; + } +} diff --git a/src/main/java/com/rabbitmq/client/AddressResolver.java b/src/main/java/com/rabbitmq/client/AddressResolver.java new file mode 100644 index 0000000000..786c5cc6b1 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/AddressResolver.java @@ -0,0 +1,52 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** Strategy interface to get the potential servers to connect to. */ +public interface AddressResolver { + + /** + * Get the potential {@link Address}es to connect to. + * + * @return candidate {@link Address}es + * @throws IOException if it encounters a problem + */ + List
getAddresses() throws IOException; + + /** + * Optionally shuffle the list of addresses returned by {@link #getAddresses()}. + * + *

The automatic connection recovery calls this method after {@link #getAddresses()} to pick a + * random address for reconnecting. + * + *

The default method implementation calls {@link Collections#shuffle(List)}. Custom + * implementations can choose to not do any shuffling to have more predictability in the + * reconnection. + * + * @param input + * @return potentially shuffled list of addresses. + */ + default List

maybeShuffle(List
input) { + List
list = new ArrayList
(input); + Collections.shuffle(list); + return list; + } +} diff --git a/src/com/rabbitmq/client/AlreadyClosedException.java b/src/main/java/com/rabbitmq/client/AlreadyClosedException.java similarity index 56% rename from src/com/rabbitmq/client/AlreadyClosedException.java rename to src/main/java/com/rabbitmq/client/AlreadyClosedException.java index f5ff58bdfd..8b281e2807 100644 --- a/src/com/rabbitmq/client/AlreadyClosedException.java +++ b/src/main/java/com/rabbitmq/client/AlreadyClosedException.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; diff --git a/src/main/java/com/rabbitmq/client/AuthenticationFailureException.java b/src/main/java/com/rabbitmq/client/AuthenticationFailureException.java new file mode 100644 index 0000000000..67c8996d97 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/AuthenticationFailureException.java @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * Thrown when the broker refuses access due to an authentication failure. + */ + +public class AuthenticationFailureException extends PossibleAuthenticationFailureException +{ + public AuthenticationFailureException(String reason) { + super(reason); + } +} diff --git a/src/com/rabbitmq/client/BasicProperties.java b/src/main/java/com/rabbitmq/client/BasicProperties.java similarity index 76% rename from src/com/rabbitmq/client/BasicProperties.java rename to src/main/java/com/rabbitmq/client/BasicProperties.java index a1b7346382..3f5e60bd82 100644 --- a/src/com/rabbitmq/client/BasicProperties.java +++ b/src/main/java/com/rabbitmq/client/BasicProperties.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; diff --git a/src/main/java/com/rabbitmq/client/BlockedCallback.java b/src/main/java/com/rabbitmq/client/BlockedCallback.java new file mode 100644 index 0000000000..bf0607b8e9 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/BlockedCallback.java @@ -0,0 +1,31 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.IOException; + +/** + * Implement this interface in order to be notified of connection block events. + * Prefer it over {@link BlockedListener} for a lambda-oriented syntax. + * @see BlockedListener + * @see UnblockedCallback + */ +@FunctionalInterface +public interface BlockedCallback { + + void handle(String reason) throws IOException; + +} diff --git a/src/main/java/com/rabbitmq/client/BlockedListener.java b/src/main/java/com/rabbitmq/client/BlockedListener.java new file mode 100644 index 0000000000..f907548f59 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/BlockedListener.java @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import java.io.IOException; + +/** + * Implement this interface in order to be notified of connection block and + * unblock events. + * For a lambda-oriented syntax, use {@link BlockedCallback} and + * {@link UnblockedCallback}. + */ +public interface BlockedListener { + void handleBlocked(String reason) throws IOException; + void handleUnblocked() throws IOException; +} diff --git a/src/main/java/com/rabbitmq/client/BuiltinExchangeType.java b/src/main/java/com/rabbitmq/client/BuiltinExchangeType.java new file mode 100644 index 0000000000..f64bbe66ea --- /dev/null +++ b/src/main/java/com/rabbitmq/client/BuiltinExchangeType.java @@ -0,0 +1,19 @@ +package com.rabbitmq.client; + +/** + * Enum for built-in exchange types. + */ +public enum BuiltinExchangeType { + + DIRECT("direct"), FANOUT("fanout"), TOPIC("topic"), HEADERS("headers"); + + private final String type; + + BuiltinExchangeType(String type) { + this.type = type; + } + + public String getType() { + return type; + } +} diff --git a/src/main/java/com/rabbitmq/client/CancelCallback.java b/src/main/java/com/rabbitmq/client/CancelCallback.java new file mode 100644 index 0000000000..1b3433032d --- /dev/null +++ b/src/main/java/com/rabbitmq/client/CancelCallback.java @@ -0,0 +1,45 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.IOException; +import java.util.Map; + +/** + * Callback interface to be notified of the cancellation of a consumer. + * Prefer it over {@link Consumer} for a lambda-oriented syntax, + * if you don't need to implement all the application callbacks. + * @see DeliverCallback + * @see ConsumerShutdownSignalCallback + * @see Channel#basicConsume(String, boolean, String, boolean, boolean, Map, DeliverCallback, CancelCallback) + * @see Channel#basicConsume(String, boolean, String, boolean, boolean, Map, DeliverCallback, ConsumerShutdownSignalCallback) + * @see Channel#basicConsume(String, boolean, String, boolean, boolean, Map, DeliverCallback, CancelCallback, ConsumerShutdownSignalCallback) + * @since 5.0 + */ +@FunctionalInterface +public interface CancelCallback { + + /** + * Called when the consumer is cancelled for reasons other than by a call to + * {@link Channel#basicCancel}. For example, the queue has been deleted. + * See {@link Consumer#handleCancelOk} for notification of consumer + * cancellation due to {@link Channel#basicCancel}. + * @param consumerTag the consumer tag associated with the consumer + * @throws IOException + */ + void handle(String consumerTag) throws IOException; + +} diff --git a/src/com/rabbitmq/client/Channel.java b/src/main/java/com/rabbitmq/client/Channel.java similarity index 54% rename from src/com/rabbitmq/client/Channel.java rename to src/main/java/com/rabbitmq/client/Channel.java index 69fd4fcdbb..9410447b6c 100644 --- a/src/com/rabbitmq/client/Channel.java +++ b/src/main/java/com/rabbitmq/client/Channel.java @@ -1,64 +1,57 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; +import com.rabbitmq.client.AMQP.BasicProperties; +import com.rabbitmq.client.AMQP.*; + import java.io.IOException; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; -import com.rabbitmq.client.AMQP.BasicProperties; -import com.rabbitmq.client.AMQP.Exchange; -import com.rabbitmq.client.AMQP.Queue; -import com.rabbitmq.client.AMQP.Tx; -import com.rabbitmq.client.AMQP.Basic; -import com.rabbitmq.client.AMQP.Confirm; - /** - * Public API: Interface to an AMQ channel. See the spec for details. + * Interface to a channel. All non-deprecated methods of + * this interface are part of the public API. * - *

- * To open a channel, - *

- * {@link Connection} conn = ...;
- * {@link Channel} channel = conn.{@link Connection#createChannel createChannel}();
- * 
- *

- * Public API: - *

    - *
  • getChannelNumber - *
  • close - *
- *

+ *

Tutorials

+ * RabbitMQ tutorials demonstrate how + * key methods of this interface are used. + * + *

User Guide

+ * See Java Client User Guide. * + *

Concurrency Considerations

*

- * {@link Channel} instances are safe for use by multiple - * threads. Requests into a {@link Channel} are serialized, with only one - * thread running commands at a time. - * As such, applications may prefer using a {@link Channel} per thread - * instead of sharing the same Channel across multiple threads. + * {@link Channel} instances must not be shared between + * threads. Applications + * should prefer using a {@link Channel} per thread + * instead of sharing the same {@link Channel} across + * multiple threads. While some operations on channels are safe to invoke + * concurrently, some are not and will result in incorrect frame interleaving + * on the wire. Sharing channels between threads will also interfere with + * Publisher Confirms. * - * An important caveat to this is that confirms are not handled - * properly when a {@link Channel} is shared between multiple threads. In that - * scenario, it is therefore important to ensure that the {@link Channel} - * instance is not accessed concurrently by multiple threads. + * As such, applications need to use a {@link Channel} per thread. + *

* + * @see RabbitMQ tutorials + * @see RabbitMQ Java Client User Guide */ - -public interface Channel extends ShutdownNotifier { +public interface Channel extends ShutdownNotifier, AutoCloseable { /** * Retrieve this channel's channel number. * @return the channel number @@ -77,7 +70,8 @@ public interface Channel extends ShutdownNotifier { * * @throws java.io.IOException if an error is encountered */ - void close() throws IOException; + @Override + void close() throws IOException, TimeoutException; /** * Close this channel. @@ -86,14 +80,7 @@ public interface Channel extends ShutdownNotifier { * @param closeMessage a message indicating the reason for closing the connection * @throws java.io.IOException if an error is encountered */ - void close(int closeCode, String closeMessage) throws IOException; - - /** - * Indicates whether the server has asked this client to stop - * sending content-bearing commands (such as basic.publish) by - * issueing a channel.flow{active=false}. - */ - boolean flowBlocked(); + void close(int closeCode, String closeMessage) throws IOException, TimeoutException; /** * Abort this channel with the {@link com.rabbitmq.client.AMQP#REPLY_SUCCESS} close code @@ -102,7 +89,7 @@ public interface Channel extends ShutdownNotifier { * Forces the channel to close and waits for the close operation to complete. * Any encountered exceptions in the close operation are silently discarded. */ - void abort() throws IOException; + void abort(); /** * Abort this channel. @@ -110,7 +97,7 @@ public interface Channel extends ShutdownNotifier { * Forces the channel to close and waits for the close operation to complete. * Any encountered exceptions in the close operation are silently discarded. */ - void abort(int closeCode, String closeMessage) throws IOException; + void abort(int closeCode, String closeMessage); /** * Add a {@link ReturnListener}. @@ -118,6 +105,16 @@ public interface Channel extends ShutdownNotifier { */ void addReturnListener(ReturnListener listener); + /** + * Add a lambda-based {@link ReturnListener}. + * @see ReturnListener + * @see ReturnCallback + * @see Return + * @param returnCallback the callback when the message is returned + * @return the listener that wraps the callback + */ + ReturnListener addReturnListener(ReturnCallback returnCallback); + /** * Remove a {@link ReturnListener}. * @param listener the listener to remove @@ -132,29 +129,20 @@ public interface Channel extends ShutdownNotifier { void clearReturnListeners(); /** - * Add a {@link FlowListener}. + * Add a {@link ConfirmListener}. * @param listener the listener to add */ - void addFlowListener(FlowListener listener); - - /** - * Remove a {@link FlowListener}. - * @param listener the listener to remove - * @return true if the listener was found and removed, - * false otherwise - */ - boolean removeFlowListener(FlowListener listener); + void addConfirmListener(ConfirmListener listener); /** - * Remove all {@link FlowListener}s. + * Add a lambda-based {@link ConfirmListener}. + * @see ConfirmListener + * @see ConfirmCallback + * @param ackCallback callback on ack + * @param nackCallback call on nack (negative ack) + * @return the listener that wraps the callbacks */ - void clearFlowListeners(); - - /** - * Add a {@link ConfirmListener}. - * @param listener the listener to add - */ - void addConfirmListener(ConfirmListener listener); + ConfirmListener addConfirmListener(ConfirmCallback ackCallback, ConfirmCallback nackCallback); /** * Remove a {@link ConfirmListener}. @@ -205,48 +193,63 @@ public interface Channel extends ShutdownNotifier { /** * Request specific "quality of service" settings. - * + *

* These settings impose limits on the amount of data the server * will deliver to consumers before requiring acknowledgements. * Thus they provide a means of consumer-initiated flow control. - * @see com.rabbitmq.client.AMQP.Basic.Qos - * @param prefetchSize maximum amount of content (measured in - * octets) that the server will deliver, 0 if unlimited + *

+ * Note the prefetch count must be between 0 and 65535 (unsigned short in AMQP 0-9-1). + * + * @param prefetchSize maximum amount of content (measured in + * octets) that the server will deliver, 0 if unlimited * @param prefetchCount maximum number of messages that the server - * will deliver, 0 if unlimited - * @param global true if the settings should be applied to the - * entire channel rather than each consumer + * will deliver, 0 if unlimited + * @param global true if the settings should be applied to the + * entire channel rather than each consumer * @throws java.io.IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Qos */ void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException; /** * Request a specific prefetchCount "quality of service" settings * for this channel. + *

+ * Note the prefetch count must be between 0 and 65535 (unsigned short in AMQP 0-9-1). * - * @see #basicQos(int, int, boolean) * @param prefetchCount maximum number of messages that the server - * will deliver, 0 if unlimited - * @param global true if the settings should be applied to the - * entire channel rather than each consumer + * will deliver, 0 if unlimited + * @param global true if the settings should be applied to the + * entire channel rather than each consumer * @throws java.io.IOException if an error is encountered + * @see #basicQos(int, int, boolean) */ void basicQos(int prefetchCount, boolean global) throws IOException; /** * Request a specific prefetchCount "quality of service" settings * for this channel. + *

+ * Note the prefetch count must be between 0 and 65535 (unsigned short in AMQP 0-9-1). * - * @see #basicQos(int, int, boolean) * @param prefetchCount maximum number of messages that the server - * will deliver, 0 if unlimited + * will deliver, 0 if unlimited * @throws java.io.IOException if an error is encountered + * @see #basicQos(int, int, boolean) */ void basicQos(int prefetchCount) throws IOException; /** - * Publish a message + * Publish a message. + * + * Publishing to a non-existent exchange will result in a channel-level + * protocol exception, which closes the channel. + * + * Invocations of Channel#basicPublish will eventually block if a + * resource-driven alarm is in effect. + * * @see com.rabbitmq.client.AMQP.Basic.Publish + * @see Resource-driven alarms * @param exchange the exchange to publish the message to * @param routingKey the routing key * @param props other properties for the message - routing headers etc @@ -256,8 +259,13 @@ public interface Channel extends ShutdownNotifier { void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException; /** - * Publish a message + * Publish a message. + * + * Invocations of Channel#basicPublish will eventually block if a + * resource-driven alarm is in effect. + * * @see com.rabbitmq.client.AMQP.Basic.Publish + * @see Resource-driven alarms * @param exchange the exchange to publish the message to * @param routingKey the routing key * @param mandatory true if the 'mandatory' flag is to be set @@ -269,8 +277,16 @@ void basicPublish(String exchange, String routingKey, boolean mandatory, BasicPr throws IOException; /** - * Publish a message + * Publish a message. + * + * Publishing to a non-existent exchange will result in a channel-level + * protocol exception, which closes the channel. + * + * Invocations of Channel#basicPublish will eventually block if a + * resource-driven alarm is in effect. + * * @see com.rabbitmq.client.AMQP.Basic.Publish + * @see Resource-driven alarms * @param exchange the exchange to publish the message to * @param routingKey the routing key * @param mandatory true if the 'mandatory' flag is to be set @@ -294,6 +310,17 @@ void basicPublish(String exchange, String routingKey, boolean mandatory, boolean */ Exchange.DeclareOk exchangeDeclare(String exchange, String type) throws IOException; + /** + * Actively declare a non-autodelete, non-durable exchange with no extra arguments + * @see com.rabbitmq.client.AMQP.Exchange.Declare + * @see com.rabbitmq.client.AMQP.Exchange.DeclareOk + * @param exchange the name of the exchange + * @param type the exchange type + * @return a declaration-confirm method to indicate the exchange was successfully declared + * @throws java.io.IOException if an error is encountered + */ + Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type) throws IOException; + /** * Actively declare a non-autodelete exchange with no extra arguments * @see com.rabbitmq.client.AMQP.Exchange.Declare @@ -306,6 +333,18 @@ void basicPublish(String exchange, String routingKey, boolean mandatory, boolean */ Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable) throws IOException; + /** + * Actively declare a non-autodelete exchange with no extra arguments + * @see com.rabbitmq.client.AMQP.Exchange.Declare + * @see com.rabbitmq.client.AMQP.Exchange.DeclareOk + * @param exchange the name of the exchange + * @param type the exchange type + * @param durable true if we are declaring a durable exchange (the exchange will survive a server restart) + * @throws java.io.IOException if an error is encountered + * @return a declaration-confirm method to indicate the exchange was successfully declared + */ + Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable) throws IOException; + /** * Declare an exchange. * @see com.rabbitmq.client.AMQP.Exchange.Declare @@ -321,6 +360,21 @@ void basicPublish(String exchange, String routingKey, boolean mandatory, boolean Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map arguments) throws IOException; + /** + * Declare an exchange. + * @see com.rabbitmq.client.AMQP.Exchange.Declare + * @see com.rabbitmq.client.AMQP.Exchange.DeclareOk + * @param exchange the name of the exchange + * @param type the exchange type + * @param durable true if we are declaring a durable exchange (the exchange will survive a server restart) + * @param autoDelete true if the server should delete the exchange when it is no longer in use + * @param arguments other properties (construction arguments) for the exchange + * @return a declaration-confirm method to indicate the exchange was successfully declared + * @throws java.io.IOException if an error is encountered + */ + Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, + Map arguments) throws IOException; + /** * Declare an exchange, via an interface that allows the complete set of * arguments. @@ -343,6 +397,28 @@ Exchange.DeclareOk exchangeDeclare(String exchange, boolean internal, Map arguments) throws IOException; + /** + * Declare an exchange, via an interface that allows the complete set of + * arguments. + * @see com.rabbitmq.client.AMQP.Exchange.Declare + * @see com.rabbitmq.client.AMQP.Exchange.DeclareOk + * @param exchange the name of the exchange + * @param type the exchange type + * @param durable true if we are declaring a durable exchange (the exchange will survive a server restart) + * @param autoDelete true if the server should delete the exchange when it is no longer in use + * @param internal true if the exchange is internal, i.e. can't be directly + * published to by a client. + * @param arguments other properties (construction arguments) for the exchange + * @return a declaration-confirm method to indicate the exchange was successfully declared + * @throws java.io.IOException if an error is encountered + */ + Exchange.DeclareOk exchangeDeclare(String exchange, + BuiltinExchangeType type, + boolean durable, + boolean autoDelete, + boolean internal, + Map arguments) throws IOException; + /** * Like {@link Channel#exchangeDeclare(String, String, boolean, boolean, java.util.Map)} but * sets nowait parameter to true and returns nothing (as there will be no response from @@ -355,7 +431,6 @@ Exchange.DeclareOk exchangeDeclare(String exchange, * @param internal true if the exchange is internal, i.e. can't be directly * published to by a client. * @param arguments other properties (construction arguments) for the exchange - * @return a declaration-confirm method to indicate the exchange was successfully declared * @throws java.io.IOException if an error is encountered */ void exchangeDeclareNoWait(String exchange, @@ -365,6 +440,27 @@ void exchangeDeclareNoWait(String exchange, boolean internal, Map arguments) throws IOException; + /** + * Like {@link Channel#exchangeDeclare(String, String, boolean, boolean, java.util.Map)} but + * sets nowait parameter to true and returns nothing (as there will be no response from + * the server). + * + * @param exchange the name of the exchange + * @param type the exchange type + * @param durable true if we are declaring a durable exchange (the exchange will survive a server restart) + * @param autoDelete true if the server should delete the exchange when it is no longer in use + * @param internal true if the exchange is internal, i.e. can't be directly + * published to by a client. + * @param arguments other properties (construction arguments) for the exchange + * @throws java.io.IOException if an error is encountered + */ + void exchangeDeclareNoWait(String exchange, + BuiltinExchangeType type, + boolean durable, + boolean autoDelete, + boolean internal, + Map arguments) throws IOException; + /** * Declare an exchange passively; that is, check if the named exchange exists. * @param name check the existence of an exchange named this @@ -411,7 +507,7 @@ void exchangeDeclareNoWait(String exchange, * @see com.rabbitmq.client.AMQP.Exchange.BindOk * @param destination the name of the exchange to which messages flow across the binding * @param source the name of the exchange from which messages flow across the binding - * @param routingKey the routine key to use for the binding + * @param routingKey the routing key to use for the binding * @return a binding-confirm method if the binding was successfully created * @throws java.io.IOException if an error is encountered */ @@ -423,7 +519,7 @@ void exchangeDeclareNoWait(String exchange, * @see com.rabbitmq.client.AMQP.Exchange.BindOk * @param destination the name of the exchange to which messages flow across the binding * @param source the name of the exchange from which messages flow across the binding - * @param routingKey the routine key to use for the binding + * @param routingKey the routing key to use for the binding * @param arguments other properties (binding parameters) * @return a binding-confirm method if the binding was successfully created * @throws java.io.IOException if an error is encountered @@ -435,7 +531,7 @@ void exchangeDeclareNoWait(String exchange, * to true and returns void (as there will be no response from the server). * @param destination the name of the exchange to which messages flow across the binding * @param source the name of the exchange from which messages flow across the binding - * @param routingKey the routine key to use for the binding + * @param routingKey the routing key to use for the binding * @param arguments other properties (binding parameters) * @throws java.io.IOException if an error is encountered */ @@ -447,7 +543,7 @@ void exchangeDeclareNoWait(String exchange, * @see com.rabbitmq.client.AMQP.Exchange.BindOk * @param destination the name of the exchange to which messages flow across the binding * @param source the name of the exchange from which messages flow across the binding - * @param routingKey the routine key to use for the binding + * @param routingKey the routing key to use for the binding * @return a binding-confirm method if the binding was successfully created * @throws java.io.IOException if an error is encountered */ @@ -459,7 +555,7 @@ void exchangeDeclareNoWait(String exchange, * @see com.rabbitmq.client.AMQP.Exchange.BindOk * @param destination the name of the exchange to which messages flow across the binding * @param source the name of the exchange from which messages flow across the binding - * @param routingKey the routine key to use for the binding + * @param routingKey the routing key to use for the binding * @param arguments other properties (binding parameters) * @return a binding-confirm method if the binding was successfully created * @throws java.io.IOException if an error is encountered @@ -471,7 +567,7 @@ void exchangeDeclareNoWait(String exchange, * and returns nothing (as there will be no response from the server). * @param destination the name of the exchange to which messages flow across the binding * @param source the name of the exchange from which messages flow across the binding - * @param routingKey the routine key to use for the binding + * @param routingKey the routing key to use for the binding * @param arguments other properties (binding parameters) * @throws java.io.IOException if an error is encountered */ @@ -569,7 +665,7 @@ void queueDeclareNoWait(String queue, boolean durable, boolean exclusive, boolea * @see com.rabbitmq.client.AMQP.Queue.BindOk * @param queue the name of the queue * @param exchange the name of the exchange - * @param routingKey the routine key to use for the binding + * @param routingKey the routing key to use for the binding * @return a binding-confirm method if the binding was successfully created * @throws java.io.IOException if an error is encountered */ @@ -581,7 +677,7 @@ void queueDeclareNoWait(String queue, boolean durable, boolean exclusive, boolea * @see com.rabbitmq.client.AMQP.Queue.BindOk * @param queue the name of the queue * @param exchange the name of the exchange - * @param routingKey the routine key to use for the binding + * @param routingKey the routing key to use for the binding * @param arguments other properties (binding parameters) * @return a binding-confirm method if the binding was successfully created * @throws java.io.IOException if an error is encountered @@ -589,12 +685,12 @@ void queueDeclareNoWait(String queue, boolean durable, boolean exclusive, boolea Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map arguments) throws IOException; /** - * Same as {@link Channel#queueDeclare(String, boolean, boolean, boolean, java.util.Map)} but sets nowait + * Same as {@link Channel#queueBind(String, String, String, java.util.Map)} but sets nowait * parameter to true and returns void (as there will be no response * from the server). * @param queue the name of the queue * @param exchange the name of the exchange - * @param routingKey the routine key to use for the binding + * @param routingKey the routing key to use for the binding * @param arguments other properties (binding parameters) * @throws java.io.IOException if an error is encountered */ @@ -606,7 +702,7 @@ void queueDeclareNoWait(String queue, boolean durable, boolean exclusive, boolea * @see com.rabbitmq.client.AMQP.Queue.UnbindOk * @param queue the name of the queue * @param exchange the name of the exchange - * @param routingKey the routine key to use for the binding + * @param routingKey the routing key to use for the binding * @return an unbinding-confirm method if the binding was successfully deleted * @throws java.io.IOException if an error is encountered */ @@ -618,7 +714,7 @@ void queueDeclareNoWait(String queue, boolean durable, boolean exclusive, boolea * @see com.rabbitmq.client.AMQP.Queue.UnbindOk * @param queue the name of the queue * @param exchange the name of the exchange - * @param routingKey the routine key to use for the binding + * @param routingKey the routing key to use for the binding * @param arguments other properties (binding parameters) * @return an unbinding-confirm method if the binding was successfully deleted * @throws java.io.IOException if an error is encountered @@ -667,7 +763,7 @@ void queueDeclareNoWait(String queue, boolean durable, boolean exclusive, boolea * Reject one or several received messages. * * Supply the deliveryTag from the {@link com.rabbitmq.client.AMQP.Basic.GetOk} - * or {@link com.rabbitmq.client.AMQP.Basic.GetOk} method containing the message to be rejected. + * or {@link com.rabbitmq.client.AMQP.Basic.Deliver} method containing the message to be rejected. * @see com.rabbitmq.client.AMQP.Basic.Nack * @param deliveryTag the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver} * @param multiple true to reject all messages up to and including @@ -705,6 +801,67 @@ void basicNack(long deliveryTag, boolean multiple, boolean requeue) */ String basicConsume(String queue, Consumer callback) throws IOException; + /** + * Start a non-nolocal, non-exclusive consumer, with + * explicit acknowledgement and a server-generated consumerTag. + * Provide access only to basic.deliver and + * basic.cancel AMQP methods (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param deliverCallback callback when a message is delivered + * @param cancelCallback callback when the consumer is cancelled + * @return the consumerTag generated by the server + * @throws IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicAck + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException; + + /** + * Start a non-nolocal, non-exclusive consumer, with + * explicit acknowledgement and a server-generated consumerTag. + * Provide access only to basic.deliver and + * shutdown signal callbacks (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param deliverCallback callback when a message is delivered + * @param shutdownSignalCallback callback when the channel/connection is shut down + * @return the consumerTag generated by the server + * @throws IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicAck + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException; + + /** + * Start a non-nolocal, non-exclusive consumer, with + * explicit acknowledgement and a server-generated consumerTag. + * Provide access to basic.deliver, basic.cancel + * and shutdown signal callbacks (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param deliverCallback callback when a message is delivered + * @param cancelCallback callback when the consumer is cancelled + * @param shutdownSignalCallback callback when the channel/connection is shut down + * @return the consumerTag generated by the server + * @throws IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicAck + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException; + /** * Start a non-nolocal, non-exclusive consumer, with * a server-generated consumerTag. @@ -721,6 +878,76 @@ void basicNack(long deliveryTag, boolean multiple, boolean requeue) */ String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException; + /** + * Start a non-nolocal, non-exclusive consumer, with + * a server-generated consumerTag. + * Provide access only to basic.deliver and + * basic.cancel AMQP methods (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param deliverCallback callback when a message is delivered + * @param cancelCallback callback when the consumer is cancelled + * @return the consumerTag generated by the server + * @throws IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicAck + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException; + + /** + * Start a non-nolocal, non-exclusive consumer, with + * a server-generated consumerTag. + * Provide access only to basic.deliver and + * shutdown signal callbacks (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param deliverCallback callback when a message is delivered + * @param shutdownSignalCallback callback when the channel/connection is shut down + * @return the consumerTag generated by the server + * @throws IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicAck + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException; + + /** + * Start a non-nolocal, non-exclusive consumer, with + * a server-generated consumerTag. + * Provide access to basic.deliver, basic.cancel + * and shutdown signal callbacks (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param deliverCallback callback when a message is delivered + * @param cancelCallback callback when the consumer is cancelled + * @param shutdownSignalCallback callback when the channel/connection is shut down + * @return the consumerTag generated by the server + * @throws IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicAck + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException; + /** * Start a non-nolocal, non-exclusive consumer, with * a server-generated consumerTag and specified arguments. @@ -738,6 +965,79 @@ void basicNack(long deliveryTag, boolean multiple, boolean requeue) */ String basicConsume(String queue, boolean autoAck, Map arguments, Consumer callback) throws IOException; + /** + * Start a non-nolocal, non-exclusive consumer, with + * a server-generated consumerTag and specified arguments. + * Provide access only to basic.deliver and + * basic.cancel AMQP methods (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param arguments a set of arguments for the consume + * @param deliverCallback callback when a message is delivered + * @param cancelCallback callback when the consumer is cancelled + * @return the consumerTag generated by the server + * @throws IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicAck + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException; + + /** + * Start a non-nolocal, non-exclusive consumer, with + * a server-generated consumerTag and specified arguments. + * Provide access only to basic.deliver and + * shutdown signal callbacks (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param arguments a set of arguments for the consume + * @param deliverCallback callback when a message is delivered + * @param shutdownSignalCallback callback when the channel/connection is shut down + * @return the consumerTag generated by the server + * @throws IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicAck + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException; + + /** + * Start a non-nolocal, non-exclusive consumer, with + * a server-generated consumerTag and specified arguments. + * Provide access to basic.deliver, basic.cancel + * and shutdown signal callbacks (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param arguments a set of arguments for the consume + * @param deliverCallback callback when a message is delivered + * @param cancelCallback callback when the consumer is cancelled + * @param shutdownSignalCallback callback when the channel/connection is shut down + * @return the consumerTag generated by the server + * @throws IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicAck + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException; + /** * Start a non-nolocal, non-exclusive consumer. * @param queue the name of the queue @@ -754,6 +1054,73 @@ void basicNack(long deliveryTag, boolean multiple, boolean requeue) */ String basicConsume(String queue, boolean autoAck, String consumerTag, Consumer callback) throws IOException; + /** + * Start a non-nolocal, non-exclusive consumer. + * Provide access only to basic.deliver and + * basic.cancel AMQP methods (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param consumerTag a client-generated consumer tag to establish context + * @param deliverCallback callback when a message is delivered + * @param cancelCallback callback when the consumer is cancelled + * @return the consumerTag associated with the new consumer + * @throws java.io.IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException; + + /** + * Start a non-nolocal, non-exclusive consumer. + * Provide access only to basic.deliver and + * shutdown signal callbacks (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param consumerTag a client-generated consumer tag to establish context + * @param deliverCallback callback when a message is delivered + * @param shutdownSignalCallback callback when the channel/connection is shut down + * @return the consumerTag associated with the new consumer + * @throws java.io.IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException; + + /** + * Start a non-nolocal, non-exclusive consumer. + * Provide access to basic.deliver, basic.cancel + * and shutdown signal callbacks (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param consumerTag a client-generated consumer tag to establish context + * @param deliverCallback callback when a message is delivered + * @param cancelCallback callback when the consumer is cancelled + * @param shutdownSignalCallback callback when the channel/connection is shut down + * @return the consumerTag associated with the new consumer + * @throws java.io.IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException; + /** * Start a consumer. Calls the consumer's {@link Consumer#handleConsumeOk} * method. @@ -762,8 +1129,8 @@ void basicNack(long deliveryTag, boolean multiple, boolean requeue) * acknowledged once delivered; false if the server should expect * explicit acknowledgements * @param consumerTag a client-generated consumer tag to establish context - * @param noLocal true if the server should not deliver to this consumer - * messages published on this channel's connection + * @param noLocal True if the server should not deliver to this consumer + * messages published on this channel's connection. Note that the RabbitMQ server does not support this flag. * @param exclusive true if this is an exclusive consumer * @param callback an interface to the consumer object * @param arguments a set of arguments for the consume @@ -774,21 +1141,104 @@ void basicNack(long deliveryTag, boolean multiple, boolean requeue) */ String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, Consumer callback) throws IOException; + /** + * Start a consumer. Calls the consumer's {@link Consumer#handleConsumeOk} + * method. + * Provide access only to basic.deliver and + * basic.cancel AMQP methods (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param consumerTag a client-generated consumer tag to establish context + * @param noLocal True if the server should not deliver to this consumer + * messages published on this channel's connection. Note that the RabbitMQ server does not support this flag. + * @param exclusive true if this is an exclusive consumer + * @param arguments a set of arguments for the consume + * @param deliverCallback callback when a message is delivered + * @param cancelCallback callback when the consumer is cancelled + * @return the consumerTag associated with the new consumer + * @throws java.io.IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException; + + /** + * Start a consumer. Calls the consumer's {@link Consumer#handleConsumeOk} + * method. + * Provide access only to basic.deliver and + * shutdown signal callbacks (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param consumerTag a client-generated consumer tag to establish context + * @param noLocal True if the server should not deliver to this consumer + * messages published on this channel's connection. Note that the RabbitMQ server does not support this flag. + * @param exclusive true if this is an exclusive consumer + * @param arguments a set of arguments for the consume + * @param deliverCallback callback when a message is delivered + * @param shutdownSignalCallback callback when the channel/connection is shut down + * @return the consumerTag associated with the new consumer + * @throws java.io.IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException; + + /** + * Start a consumer. Calls the consumer's {@link Consumer#handleConsumeOk} + * method. + * Provide access to basic.deliver, basic.cancel + * and shutdown signal callbacks (which is sufficient + * for most cases). See methods with a {@link Consumer} argument + * to have access to all the application callbacks. + * @param queue the name of the queue + * @param autoAck true if the server should consider messages + * acknowledged once delivered; false if the server should expect + * explicit acknowledgements + * @param consumerTag a client-generated consumer tag to establish context + * @param noLocal True if the server should not deliver to this consumer + * messages published on this channel's connection. Note that the RabbitMQ server does not support this flag. + * @param exclusive true if this is an exclusive consumer + * @param arguments a set of arguments for the consume + * @param deliverCallback callback when a message is delivered + * @param cancelCallback callback when the consumer is cancelled + * @param shutdownSignalCallback callback when the channel/connection is shut down + * @return the consumerTag associated with the new consumer + * @throws java.io.IOException if an error is encountered + * @see com.rabbitmq.client.AMQP.Basic.Consume + * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk + * @since 5.0 + */ + String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException; + /** * Cancel a consumer. Calls the consumer's {@link Consumer#handleCancelOk} * method. + *

+ * A consumer tag that does not match any consumer is ignored. + * * @param consumerTag a client- or server-generated consumer tag to establish context - * @throws IOException if an error is encountered, or if the consumerTag is unknown + * @throws IOException if an error is encountered * @see com.rabbitmq.client.AMQP.Basic.Cancel * @see com.rabbitmq.client.AMQP.Basic.CancelOk */ void basicCancel(String consumerTag) throws IOException; /** - * Ask the broker to resend unacknowledged messages. In 0-8 + *

+ * Ask the broker to resend unacknowledged messages. In 0-8 * basic.recover is asynchronous; in 0-9-1 it is synchronous, and * the new, deprecated method basic.recover_async is asynchronous. - *

+ *

* Equivalent to calling basicRecover(true), messages * will be requeued and possibly delivered to a different consumer. * @see #basicRecover(boolean) @@ -899,4 +1349,33 @@ void basicNack(long deliveryTag, boolean multiple, boolean requeue) * @throws IOException Problem transmitting method. */ Command rpc(Method method) throws IOException; + + /** + * Returns the number of messages in a queue ready to be delivered + * to consumers. This method assumes the queue exists. If it doesn't, + * the channels will be closed with an exception. + * @param queue the name of the queue + * @return the number of messages in ready state + * @throws IOException Problem transmitting method. + */ + long messageCount(String queue) throws IOException; + + /** + * Returns the number of consumers on a queue. + * This method assumes the queue exists. If it doesn't, + * the channel will be closed with an exception. + * @param queue the name of the queue + * @return the number of consumers + * @throws IOException Problem transmitting method. + */ + long consumerCount(String queue) throws IOException; + + /** + * Asynchronously send a method over this channel. + * @param method method to transmit over this channel. + * @return a completable future that completes when the result is received + * @throws IOException Problem transmitting method. + */ + CompletableFuture asyncCompletableRpc(Method method) throws IOException; + } diff --git a/src/main/java/com/rabbitmq/client/ChannelContinuationTimeoutException.java b/src/main/java/com/rabbitmq/client/ChannelContinuationTimeoutException.java new file mode 100644 index 0000000000..d5bca1eeba --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ChannelContinuationTimeoutException.java @@ -0,0 +1,63 @@ +package com.rabbitmq.client; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +/** + * Exception thrown when a channel times out on a continuation during a RPC call. + * @since 4.1.0 + */ +public class ChannelContinuationTimeoutException extends IOException { + + /** + * The channel that performed the call. + * Typed as Object as the underlying + * object that performs the call might + * not be an implementation of {@link Channel}. + */ + private final Object channel; + + /** + * The number of the channel that performed the call. + */ + private final int channelNumber; + + /** + * The request method that timed out. + */ + private final Method method; + + public ChannelContinuationTimeoutException(TimeoutException cause, Object channel, int channelNumber, Method method) { + super( + "Continuation call for method " + method + " on channel " + channel + " (#" + channelNumber + ") timed out", + cause + ); + this.channel = channel; + this.channelNumber = channelNumber; + this.method = method; + } + + /** + * + * @return request method that timed out + */ + public Method getMethod() { + return method; + } + + /** + * channel that performed the call + * @return + */ + public Object getChannel() { + return channel; + } + + /** + * + * @return number of the channel that performed the call + */ + public int getChannelNumber() { + return channelNumber; + } +} diff --git a/src/com/rabbitmq/client/Command.java b/src/main/java/com/rabbitmq/client/Command.java similarity index 55% rename from src/com/rabbitmq/client/Command.java rename to src/main/java/com/rabbitmq/client/Command.java index 4938f15521..fe3a3221fe 100644 --- a/src/com/rabbitmq/client/Command.java +++ b/src/main/java/com/rabbitmq/client/Command.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; diff --git a/src/main/java/com/rabbitmq/client/ConfirmCallback.java b/src/main/java/com/rabbitmq/client/ConfirmCallback.java new file mode 100644 index 0000000000..cd0e8fe597 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ConfirmCallback.java @@ -0,0 +1,36 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import java.io.IOException; + +/** + * Implement this interface in order to be notified of Confirm events. + * Acks represent messages handled successfully; Nacks represent + * messages lost by the broker. Note, the lost messages could still + * have been delivered to consumers, but the broker cannot guarantee + * this. + * Prefer this interface over {@link ConfirmListener} for + * a lambda-oriented syntax. + * @see ConfirmListener + */ +@FunctionalInterface +public interface ConfirmCallback { + + void handle(long deliveryTag, boolean multiple) throws IOException; + +} diff --git a/src/main/java/com/rabbitmq/client/ConfirmListener.java b/src/main/java/com/rabbitmq/client/ConfirmListener.java new file mode 100644 index 0000000000..c6347ec0c5 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ConfirmListener.java @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import java.io.IOException; + +/** + * Implement this interface in order to be notified of Confirm events. + * Acks represent messages handled successfully; Nacks represent + * messages lost by the broker. Note, the lost messages could still + * have been delivered to consumers, but the broker cannot guarantee + * this. + * For a lambda-oriented syntax, use {@link ConfirmCallback}. + */ +public interface ConfirmListener { + void handleAck(long deliveryTag, boolean multiple) + throws IOException; + + void handleNack(long deliveryTag, boolean multiple) + throws IOException; +} diff --git a/src/com/rabbitmq/client/Connection.java b/src/main/java/com/rabbitmq/client/Connection.java similarity index 62% rename from src/com/rabbitmq/client/Connection.java rename to src/main/java/com/rabbitmq/client/Connection.java index 3abfaa2226..131d456180 100644 --- a/src/com/rabbitmq/client/Connection.java +++ b/src/main/java/com/rabbitmq/client/Connection.java @@ -1,27 +1,29 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; +import java.io.Closeable; import java.io.IOException; import java.net.InetAddress; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutorService; /** - * Public API: Interface to an AMQ connection. See the see the spec for details. + * Public API: Interface to an AMQ connection. See the see the spec for details. *

* To connect to a broker, fill in a {@link ConnectionFactory} and use a {@link ConnectionFactory} as follows: * @@ -50,7 +52,7 @@ * Current implementations are thread-safe for code at the client API level, * and in fact thread-safe internally except for code within RPC calls. */ -public interface Connection extends ShutdownNotifier { // rename to AMQPConnection later, this is a temporary name +public interface Connection extends ShutdownNotifier, Closeable { // rename to AMQPConnection later, this is a temporary name /** * Retrieve the host. * @return the hostname of the peer we're connected to. @@ -93,6 +95,19 @@ public interface Connection extends ShutdownNotifier { // rename to AMQPConnecti */ Map getClientProperties(); + /** + * Returns client-provided connection name, if any. Note that the value + * returned does not uniquely identify a connection and cannot be used + * as a connection identifier in HTTP API requests. + * + * + * + * @return client-provided connection name, if any + * @see ConnectionFactory#newConnection(Address[], String) + * @see ConnectionFactory#newConnection(ExecutorService, Address[], String) + */ + String getClientProvidedName(); + /** * Retrieve the server properties. * @return a map of the server properties. This typically includes the product name and version of the server. @@ -101,6 +116,12 @@ public interface Connection extends ShutdownNotifier { // rename to AMQPConnecti /** * Create a new channel, using an internally allocated channel number. + * If automatic connection recovery + * is enabled, the channel returned by this method will be {@link Recoverable}. + *

+ * Use {@link #openChannel()} if you want to use an {@link Optional} to deal + * with a {@null} value. + * * @return a new channel descriptor, or null if none is available * @throws IOException if an I/O problem is encountered */ @@ -108,12 +129,51 @@ public interface Connection extends ShutdownNotifier { // rename to AMQPConnecti /** * Create a new channel, using the specified channel number if possible. + *

+ * Use {@link #openChannel(int)} if you want to use an {@link Optional} to deal + * with a {@null} value. + * * @param channelNumber the channel number to allocate * @return a new channel descriptor, or null if this channel number is already in use * @throws IOException if an I/O problem is encountered */ Channel createChannel(int channelNumber) throws IOException; + /** + * Create a new channel wrapped in an {@link Optional}. + * The channel number is allocated internally. + *

+ * If automatic connection recovery + * is enabled, the channel returned by this method will be {@link Recoverable}. + *

+ * Use {@link #createChannel()} to return directly a {@link Channel} or {@code null}. + * + * @return an {@link Optional} containing the channel; + * never {@code null} but potentially empty if no channel is available + * @throws IOException if an I/O problem is encountered + * @see #createChannel() + * @since 5.6.0 + */ + default Optional openChannel() throws IOException { + return Optional.ofNullable(createChannel()); + } + + /** + * Create a new channel, using the specified channel number if possible. + *

+ * Use {@link #createChannel(int)} to return directly a {@link Channel} or {@code null}. + * + * @param channelNumber the channel number to allocate + * @return an {@link Optional} containing the channel, + * never {@code null} but potentially empty if this channel number is already in use + * @throws IOException if an I/O problem is encountered + * @see #createChannel(int) + * @since 5.6.0 + */ + default Optional openChannel(int channelNumber) throws IOException { + return Optional.ofNullable(createChannel(channelNumber)); + } + /** * Close this connection and all its channels * with the {@link com.rabbitmq.client.AMQP#REPLY_SUCCESS} close code @@ -123,6 +183,7 @@ public interface Connection extends ShutdownNotifier { // rename to AMQPConnecti * * @throws IOException if an I/O problem is encountered */ + @Override void close() throws IOException; /** @@ -221,6 +282,17 @@ public interface Connection extends ShutdownNotifier { // rename to AMQPConnecti */ void addBlockedListener(BlockedListener listener); + /** + * Add a lambda-based {@link BlockedListener}. + * @see BlockedListener + * @see BlockedCallback + * @see UnblockedCallback + * @param blockedCallback the callback when the connection is blocked + * @param unblockedCallback the callback when the connection is unblocked + * @return the listener that wraps the callback + */ + BlockedListener addBlockedListener(BlockedCallback blockedCallback, UnblockedCallback unblockedCallback); + /** * Remove a {@link BlockedListener}. * @param listener the listener to remove @@ -240,4 +312,28 @@ public interface Connection extends ShutdownNotifier { // rename to AMQPConnecti * @see com.rabbitmq.client.ExceptionHandler */ ExceptionHandler getExceptionHandler(); + + /** + * Returns a unique ID for this connection. + * + * This ID must be unique, otherwise some services + * like the metrics collector won't work properly. + * This ID doesn't have to be provided by the client, + * services that require it will be assigned automatically + * if not set. + * + * @return unique ID for this connection. + */ + String getId(); + + /** + * Sets a unique ID for this connection. + * + * This ID must be unique, otherwise some services + * like the metrics collector won't work properly. + * This ID doesn't have to be provided by the client, + * services that require it will be assigned automatically + * if not set. + */ + void setId(String id); } diff --git a/src/main/java/com/rabbitmq/client/ConnectionFactory.java b/src/main/java/com/rabbitmq/client/ConnectionFactory.java new file mode 100644 index 0000000000..39c219cef6 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ConnectionFactory.java @@ -0,0 +1,1765 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import com.rabbitmq.client.impl.*; +import com.rabbitmq.client.impl.nio.NioParams; +import com.rabbitmq.client.impl.nio.SocketChannelFrameHandlerFactory; +import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; +import com.rabbitmq.client.impl.recovery.RecoveredQueueNameSupplier; +import com.rabbitmq.client.impl.recovery.RetryHandler; +import com.rabbitmq.client.impl.recovery.TopologyRecoveryFilter; +import com.rabbitmq.client.observation.ObservationCollector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.*; +import java.util.function.BiConsumer; +import java.util.function.Predicate; + +import static java.util.concurrent.TimeUnit.MINUTES; + +/** + * Convenience factory class to facilitate opening a {@link Connection} to a RabbitMQ node. + * + * Most connection and socket settings are configured using this factory. + * Some settings that apply to connections can also be configured here + * and will apply to all connections produced by this factory. + */ +public class ConnectionFactory implements Cloneable { + + private static final int MAX_UNSIGNED_SHORT = 65535; + + /** Default user name */ + public static final String DEFAULT_USER = "guest"; + /** Default password */ + public static final String DEFAULT_PASS = "guest"; + /** Default virtual host */ + public static final String DEFAULT_VHOST = "/"; + /** Default maximum channel number; + * 2047 because it's 2048 on the server side minus channel 0, + * which each connection uses for negotiation + * and error communication */ + public static final int DEFAULT_CHANNEL_MAX = 2047; + /** Default maximum frame size; + * zero means no limit */ + public static final int DEFAULT_FRAME_MAX = 0; + /** Default heart-beat interval; + * 60 seconds */ + public static final int DEFAULT_HEARTBEAT = 60; + /** The default host */ + public static final String DEFAULT_HOST = "localhost"; + /** 'Use the default port' port */ + public static final int USE_DEFAULT_PORT = -1; + /** The default non-ssl port */ + public static final int DEFAULT_AMQP_PORT = AMQP.PROTOCOL.PORT; + /** The default ssl port */ + public static final int DEFAULT_AMQP_OVER_SSL_PORT = 5671; + /** The default TCP connection timeout: 60 seconds */ + public static final int DEFAULT_CONNECTION_TIMEOUT = 60000; + /** + * The default AMQP 0-9-1 connection handshake timeout. See DEFAULT_CONNECTION_TIMEOUT + * for TCP (socket) connection timeout. + */ + public static final int DEFAULT_HANDSHAKE_TIMEOUT = 10000; + /** The default shutdown timeout; + * zero means wait indefinitely */ + public static final int DEFAULT_SHUTDOWN_TIMEOUT = 10000; + + /** The default continuation timeout for RPC calls in channels: 10 minutes */ + public static final int DEFAULT_CHANNEL_RPC_TIMEOUT = (int) MINUTES.toMillis(10); + + /** The default network recovery interval: 5000 millis */ + public static final long DEFAULT_NETWORK_RECOVERY_INTERVAL = 5000; + + /** The default timeout for work pool enqueueing: no timeout */ + public static final int DEFAULT_WORK_POOL_TIMEOUT = -1; + + private static final String PREFERRED_TLS_PROTOCOL = "TLSv1.2"; + + private static final String FALLBACK_TLS_PROTOCOL = "TLSv1"; + + private String virtualHost = DEFAULT_VHOST; + private String host = DEFAULT_HOST; + private int port = USE_DEFAULT_PORT; + private int requestedChannelMax = DEFAULT_CHANNEL_MAX; + private int requestedFrameMax = DEFAULT_FRAME_MAX; + private int requestedHeartbeat = DEFAULT_HEARTBEAT; + private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; + private int handshakeTimeout = DEFAULT_HANDSHAKE_TIMEOUT; + private int shutdownTimeout = DEFAULT_SHUTDOWN_TIMEOUT; + private Map _clientProperties = AMQConnection.defaultClientProperties(); + private SocketFactory socketFactory = null; + private SaslConfig saslConfig = DefaultSaslConfig.PLAIN; + + private ExecutorService sharedExecutor; + private ThreadFactory threadFactory = Executors.defaultThreadFactory(); + // minimises the number of threads rapid closure of many + // connections uses, see rabbitmq/rabbitmq-java-client#86 + private ExecutorService shutdownExecutor; + private ScheduledExecutorService heartbeatExecutor; + private SocketConfigurator socketConf = SocketConfigurators.defaultConfigurator(); + private ExceptionHandler exceptionHandler = new DefaultExceptionHandler(); + private CredentialsProvider credentialsProvider = new DefaultCredentialsProvider(DEFAULT_USER, DEFAULT_PASS); + + private boolean automaticRecovery = true; + private boolean topologyRecovery = true; + private ExecutorService topologyRecoveryExecutor; + + // long is used to make sure the users can use both ints + // and longs safely. It is unlikely that anybody'd need + // to use recovery intervals > Integer.MAX_VALUE in practice. + private long networkRecoveryInterval = DEFAULT_NETWORK_RECOVERY_INTERVAL; + private RecoveryDelayHandler recoveryDelayHandler; + + private MetricsCollector metricsCollector; + private ObservationCollector observationCollector = ObservationCollector.NO_OP; + + private boolean nio = false; + private FrameHandlerFactory frameHandlerFactory; + private NioParams nioParams = new NioParams(); + + private SslContextFactory sslContextFactory; + + /** + * Continuation timeout on RPC calls. + * @since 4.1.0 + */ + private int channelRpcTimeout = DEFAULT_CHANNEL_RPC_TIMEOUT; + + /** + * Whether or not channels check the reply type of an RPC call. + * Default is false. + * @since 4.2.0 + */ + private boolean channelShouldCheckRpcResponseType = false; + + /** + * Listener called when a connection gets an IO error trying to write on the socket. + * Default listener triggers connection recovery asynchronously and propagates + * the exception. + * @since 4.5.0 + */ + private ErrorOnWriteListener errorOnWriteListener; + + /** + * Timeout in ms for work pool enqueuing. + * @since 4.5.0 + */ + private int workPoolTimeout = DEFAULT_WORK_POOL_TIMEOUT; + + /** + * Filter to include/exclude entities from topology recovery. + * @since 4.8.0 + */ + private TopologyRecoveryFilter topologyRecoveryFilter; + + /** + * Condition to trigger automatic connection recovery. + * @since 5.4.0 + */ + private Predicate connectionRecoveryTriggeringCondition; + + /** + * Retry handler for topology recovery. + * Default is no retry. + * @since 5.4.0 + */ + private RetryHandler topologyRecoveryRetryHandler; + private RecoveredQueueNameSupplier recoveredQueueNameSupplier; + + /** + * Traffic listener notified of inbound and outbound {@link Command}s. + *

+ * Useful for debugging purposes. Default is no-op. + * + * @since 5.5.0 + */ + private TrafficListener trafficListener = TrafficListener.NO_OP; + + private CredentialsRefreshService credentialsRefreshService; + + /** + * Maximum body size of inbound (received) messages in bytes. + * + *

Default value is 67,108,864 (64 MiB). + */ + private int maxInboundMessageBodySize = 1_048_576 * 64; + + /** @return the default host to use for connections */ + public String getHost() { + return host; + } + + /** @param host the default host to use for connections */ + public ConnectionFactory setHost(String host) { + this.host = host; + return this; + } + + public static int portOrDefault(int port, boolean ssl) { + if (port != USE_DEFAULT_PORT) return port; + else if (ssl) return DEFAULT_AMQP_OVER_SSL_PORT; + else return DEFAULT_AMQP_PORT; + } + + /** @return the default port to use for connections */ + public int getPort() { + return portOrDefault(port, isSSL()); + } + + /** + * Set the target port. + * @param port the default port to use for connections + */ + public ConnectionFactory setPort(int port) { + this.port = port; + return this; + } + + /** + * Retrieve the user name. + * @return the AMQP user name to use when connecting to the broker + */ + public String getUsername() { + return credentialsProvider.getUsername(); + } + + /** + * Set the user name. + * @param username the AMQP user name to use when connecting to the broker + */ + public ConnectionFactory setUsername(String username) { + this.credentialsProvider = new DefaultCredentialsProvider( + username, + this.credentialsProvider.getPassword() + ); + return this; + } + + /** + * Retrieve the password. + * @return the password to use when connecting to the broker + */ + public String getPassword() { + return credentialsProvider.getPassword(); + } + + /** + * Set the password. + * @param password the password to use when connecting to the broker + */ + public ConnectionFactory setPassword(String password) { + this.credentialsProvider = new DefaultCredentialsProvider( + this.credentialsProvider.getUsername(), + password + ); + return this; + } + + /** + * Set a custom credentials provider. + * Default implementation uses static username and password. + * @param credentialsProvider The custom implementation of CredentialsProvider to use when connecting to the broker. + * @see com.rabbitmq.client.impl.DefaultCredentialsProvider + * @since 4.5.0 + */ + public ConnectionFactory setCredentialsProvider(CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Retrieve the virtual host. + * @return the virtual host to use when connecting to the broker + */ + public String getVirtualHost() { + return this.virtualHost; + } + + /** + * Set the virtual host. + * @param virtualHost the virtual host to use when connecting to the broker + */ + public ConnectionFactory setVirtualHost(String virtualHost) { + this.virtualHost = virtualHost; + return this; + } + + + /** + * Convenience method for setting the fields in an AMQP URI: host, + * port, username, password and virtual host. If any part of the + * URI is omitted, the ConnectionFactory's corresponding variable + * is left unchanged. + * @param uri is the AMQP URI containing the data + */ + public ConnectionFactory setUri(URI uri) + throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException + { + if ("amqp".equals(uri.getScheme().toLowerCase())) { + // nothing special to do + } else if ("amqps".equals(uri.getScheme().toLowerCase())) { + setPort(DEFAULT_AMQP_OVER_SSL_PORT); + // SSL context factory not set yet, we use the default one + if (this.sslContextFactory == null) { + useSslProtocol(); + } + } else { + throw new IllegalArgumentException("Wrong scheme in AMQP URI: " + + uri.getScheme()); + } + + String host = uri.getHost(); + if (host != null) { + setHost(host); + } + + int port = uri.getPort(); + if (port != -1) { + setPort(port); + } + + String userInfo = uri.getRawUserInfo(); + if (userInfo != null) { + String userPass[] = userInfo.split(":"); + if (userPass.length > 2) { + throw new IllegalArgumentException("Bad user info in AMQP " + + "URI: " + userInfo); + } + + setUsername(uriDecode(userPass[0])); + if (userPass.length == 2) { + setPassword(uriDecode(userPass[1])); + } + } + + String path = uri.getRawPath(); + if (path != null && path.length() > 0) { + if (path.indexOf('/', 1) != -1) { + throw new IllegalArgumentException("Multiple segments in " + + "path of AMQP URI: " + + path); + } + + setVirtualHost(uriDecode(uri.getPath().substring(1))); + } + + String rawQuery = uri.getRawQuery(); + if (rawQuery != null && rawQuery.length() > 0) { + setQuery(rawQuery); + } + return this; + } + + /** + * Convenience method for setting the fields in an AMQP URI: host, + * port, username, password and virtual host. If any part of the + * URI is omitted, the ConnectionFactory's corresponding variable + * is left unchanged. Note that not all valid AMQP URIs are + * accepted; in particular, the hostname must be given if the + * port, username or password are given, and escapes in the + * hostname are not permitted. + * @param uriString is the AMQP URI containing the data + */ + public ConnectionFactory setUri(String uriString) + throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException + { + setUri(new URI(uriString)); + return this; + } + + private static String uriDecode(String s) { + try { + // URLDecode decodes '+' to a space, as for + // form encoding. So protect plus signs. + return URLDecoder.decode(s.replace("+", "%2B"), "US-ASCII"); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static final Map> URI_QUERY_PARAMETER_HANDLERS = + new HashMap>() { + { + put("heartbeat", (value, cf) -> { + try { + int heartbeatInt = Integer.parseInt(value); + cf.setRequestedHeartbeat(heartbeatInt); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Requested heartbeat must an integer"); + } + }); + put("connection_timeout", (value, cf) -> { + try { + int connectionTimeoutInt = Integer.parseInt(value); + cf.setConnectionTimeout(connectionTimeoutInt); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("TCP connection timeout must an integer"); + } + }); + put("channel_max", (value, cf) -> { + try { + int channelMaxInt = Integer.parseInt(value); + cf.setRequestedChannelMax(channelMaxInt); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Requested channel max must an integer"); + } + }); + } + }; + + /** + * Convenience method for setting some fields from query parameters + * Will handle only a subset of the query parameters supported by the + * official erlang client + * https://www.rabbitmq.com/uri-query-parameters.html + * @param rawQuery is the string containing the raw query parameters part from a URI + */ + private ConnectionFactory setQuery(String rawQuery) { + Map parameters = new HashMap<>(); + // parsing the query parameters + try { + for (String param : rawQuery.split("&")) { + String[] pair = param.split("="); + String key = URLDecoder.decode(pair[0], "US-ASCII"); + String value = null; + if (pair.length > 1) { + value = URLDecoder.decode(pair[1], "US-ASCII"); + } + parameters.put(key, value); + } + } catch (IOException e) { + throw new IllegalArgumentException("Cannot parse the query parameters", e); + } + + for (Entry entry : parameters.entrySet()) { + BiConsumer handler = URI_QUERY_PARAMETER_HANDLERS + .get(entry.getKey()); + if (handler != null) { + handler.accept(entry.getValue(), this); + } else { + processUriQueryParameter(entry.getKey(), entry.getValue()); + } + } + return this; + } + + /** + * Hook to process query parameters not handled natively. + * Handled natively: heartbeat, connection_timeout, + * channel_max. + * @param key + * @param value + */ + protected void processUriQueryParameter(String key, String value) { + + } + + /** + * Retrieve the requested maximum channel number + * @return the initially requested maximum channel number; zero for unlimited + */ + public int getRequestedChannelMax() { + return this.requestedChannelMax; + } + + /** + * Set the requested maximum channel number. + *

+ * Note the value must be between 0 and 65535 (unsigned short in AMQP 0-9-1). + * + * @param requestedChannelMax initially requested maximum channel number; zero for unlimited + */ + public ConnectionFactory setRequestedChannelMax(int requestedChannelMax) { + if (requestedChannelMax < 0 || requestedChannelMax > MAX_UNSIGNED_SHORT) { + throw new IllegalArgumentException("Requested channel max must be between 0 and " + MAX_UNSIGNED_SHORT); + } + this.requestedChannelMax = requestedChannelMax; + return this; + } + + /** + * Retrieve the requested maximum frame size + * @return the initially requested maximum frame size, in octets; zero for unlimited + */ + public int getRequestedFrameMax() { + return this.requestedFrameMax; + } + + /** + * Set the requested maximum frame size + * @param requestedFrameMax initially requested maximum frame size, in octets; zero for unlimited + */ + public ConnectionFactory setRequestedFrameMax(int requestedFrameMax) { + this.requestedFrameMax = requestedFrameMax; + return this; + } + + /** + * Retrieve the requested heartbeat interval. + * @return the initially requested heartbeat interval, in seconds; zero for none + */ + public int getRequestedHeartbeat() { + return this.requestedHeartbeat; + } + + /** + * Set the TCP connection timeout. + * @param timeout connection TCP establishment timeout in milliseconds; zero for infinite + */ + public ConnectionFactory setConnectionTimeout(int timeout) { + if(timeout < 0) { + throw new IllegalArgumentException("TCP connection timeout cannot be negative"); + } + this.connectionTimeout = timeout; + return this; + } + + /** + * Retrieve the TCP connection timeout. + * @return the TCP connection timeout, in milliseconds; zero for infinite + */ + public int getConnectionTimeout() { + return this.connectionTimeout; + } + + /** + * Retrieve the AMQP 0-9-1 protocol handshake timeout. + * @return the AMQP0-9-1 protocol handshake timeout, in milliseconds + */ + public int getHandshakeTimeout() { + return handshakeTimeout; + } + + /** + * Set the AMQP0-9-1 protocol handshake timeout. + * @param timeout the AMQP0-9-1 protocol handshake timeout, in milliseconds + */ + public ConnectionFactory setHandshakeTimeout(int timeout) { + if(timeout < 0) { + throw new IllegalArgumentException("handshake timeout cannot be negative"); + } + this.handshakeTimeout = timeout; + return this; + } + + /** + * Set the shutdown timeout. This is the amount of time that Consumer implementations have to + * continue working through deliveries (and other Consumer callbacks) after the connection + * has closed but before the ConsumerWorkService is torn down. If consumers exceed this timeout + * then any remaining queued deliveries (and other Consumer callbacks, including + * the Consumer's handleShutdownSignal() invocation) will be lost. + * @param shutdownTimeout shutdown timeout in milliseconds; zero for infinite; default 10000 + */ + public ConnectionFactory setShutdownTimeout(int shutdownTimeout) { + this.shutdownTimeout = shutdownTimeout; + return this; + } + + /** + * Retrieve the shutdown timeout. + * @return the shutdown timeout, in milliseconds; zero for infinite + */ + public int getShutdownTimeout() { + return shutdownTimeout; + } + + /** + * Set the requested heartbeat timeout. Heartbeat frames will be sent at about 1/2 the timeout interval. + * If server heartbeat timeout is configured to a non-zero value, this method can only be used + * to lower the value; otherwise any value provided by the client will be used. + *

+ * Note the value must be between 0 and 65535 (unsigned short in AMQP 0-9-1). + * + * @param requestedHeartbeat the initially requested heartbeat timeout, in seconds; zero for none + * @see RabbitMQ Heartbeats Guide + */ + public ConnectionFactory setRequestedHeartbeat(int requestedHeartbeat) { + if (requestedHeartbeat < 0 || requestedHeartbeat > MAX_UNSIGNED_SHORT) { + throw new IllegalArgumentException("Requested heartbeat must be between 0 and " + MAX_UNSIGNED_SHORT); + } + this.requestedHeartbeat = requestedHeartbeat; + return this; + } + + /** + * Retrieve the currently-configured table of client properties + * that will be sent to the server during connection + * startup. Clients may add, delete, and alter keys in this + * table. Such changes will take effect when the next new + * connection is started using this factory. + * @return the map of client properties + * @see #setClientProperties + */ + public Map getClientProperties() { + return _clientProperties; + } + + /** + * Replace the table of client properties that will be sent to the + * server during subsequent connection startups. + * @param clientProperties the map of extra client properties + * @see #getClientProperties + */ + public ConnectionFactory setClientProperties(Map clientProperties) { + this._clientProperties = clientProperties; + return this; + } + + /** + * Gets the sasl config to use when authenticating + * @return the sasl config + * @see com.rabbitmq.client.SaslConfig + */ + public SaslConfig getSaslConfig() { + return saslConfig; + } + + /** + * Sets the sasl config to use when authenticating + * @param saslConfig + * @see com.rabbitmq.client.SaslConfig + */ + public ConnectionFactory setSaslConfig(SaslConfig saslConfig) { + this.saslConfig = saslConfig; + return this; + } + + /** + * Retrieve the socket factory used to make connections with. + */ + public SocketFactory getSocketFactory() { + return this.socketFactory; + } + + /** + * Set the socket factory used to create sockets for new connections. Can be + * used to customize TLS-related settings by passing in a + * javax.net.ssl.SSLSocketFactory instance. + * Note this applies only to blocking IO, not to + * NIO, as the NIO API doesn't use the SocketFactory API. + * @see #useSslProtocol + */ + public ConnectionFactory setSocketFactory(SocketFactory factory) { + this.socketFactory = factory; + return this; + } + + /** + * Get the socket configurator. + * + * @see #setSocketConfigurator(SocketConfigurator) + */ + public SocketConfigurator getSocketConfigurator() { + return socketConf; + } + + /** + * Set the socket configurator. This gets a chance to "configure" a socket + * before it has been opened. The default socket configurator disables + * Nagle's algorithm. + * + * @param socketConfigurator the configurator to use + */ + public ConnectionFactory setSocketConfigurator(SocketConfigurator socketConfigurator) { + this.socketConf = socketConfigurator; + return this; + } + + /** + * Set the executor to use for consumer operation dispatch + * by default for newly created connections. + * All connections that use this executor share it. + * + * It's developer's responsibility to shut down the executor + * when it is no longer needed. + * + * @param executor executor service to be used for + * consumer operation + */ + public ConnectionFactory setSharedExecutor(ExecutorService executor) { + this.sharedExecutor = executor; + return this; + } + + /** + * Set the executor to use for connection shutdown. + * All connections that use this executor share it. + * + * It's developer's responsibility to shut down the executor + * when it is no longer needed. + * + * @param executor executor service to be used for + * connection shutdown + */ + public ConnectionFactory setShutdownExecutor(ExecutorService executor) { + this.shutdownExecutor = executor; + return this; + } + + /** + * Set the executor to use to send heartbeat frames. + * All connections that use this executor share it. + * + * It's developer's responsibility to shut down the executor + * when it is no longer needed. + * + * @param executor executor service to be used to send heartbeat + */ + public ConnectionFactory setHeartbeatExecutor(ScheduledExecutorService executor) { + this.heartbeatExecutor = executor; + return this; + } + + /** + * Retrieve the thread factory used to instantiate new threads. + * @see ThreadFactory + */ + public ThreadFactory getThreadFactory() { + return threadFactory; + } + + /** + * Set the thread factory used to instantiate new threads. + * @see ThreadFactory + */ + public ConnectionFactory setThreadFactory(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + return this; + } + + /** + * Get the exception handler. + * + * @see com.rabbitmq.client.ExceptionHandler + */ + public ExceptionHandler getExceptionHandler() { + return exceptionHandler; + } + + /** + * Set the exception handler to use for newly created connections. + * @see com.rabbitmq.client.ExceptionHandler + */ + public ConnectionFactory setExceptionHandler(ExceptionHandler exceptionHandler) { + if (exceptionHandler == null) { + throw new IllegalArgumentException("exception handler cannot be null!"); + } + this.exceptionHandler = exceptionHandler; + return this; + } + + public boolean isSSL(){ + return getSocketFactory() instanceof SSLSocketFactory || sslContextFactory != null; + } + + /** + * Convenience method for configuring TLS using + * the default set of TLS protocols and a trusting TrustManager. + * This setup is only suitable for development + * and QA environments. + * The trust manager will trust every server certificate presented + * to it, this is convenient for local development but + * not recommended to use in production as it provides no protection + * against man-in-the-middle attacks. Prefer {@link #useSslProtocol(SSLContext)}. + */ + public ConnectionFactory useSslProtocol() + throws NoSuchAlgorithmException, KeyManagementException + { + return useSslProtocol(computeDefaultTlsProtocol(SSLContext.getDefault().getSupportedSSLParameters().getProtocols())); + } + + /** + * Convenience method for configuring TLS using + * the supplied protocol and a very trusting TrustManager. This setup is only suitable for development + * and QA environments. + * The trust manager will trust every server certificate presented + * to it, this is convenient for local development but + * not recommended to use in production as it provides no protection + * against man-in-the-middle attacks. + * + * Use {@link #useSslProtocol(SSLContext)} in production environments. + * The produced {@link SSLContext} instance will be shared by all + * the connections created by this connection factory. + * + * Use {@link #setSslContextFactory(SslContextFactory)} for more flexibility. + * @see #setSslContextFactory(SslContextFactory) + */ + public ConnectionFactory useSslProtocol(String protocol) + throws NoSuchAlgorithmException, KeyManagementException + { + return useSslProtocol(protocol, new TrustEverythingTrustManager()); + } + + /** + * Convenience method for configuring TLS. + * Pass in the TLS protocol version to use, e.g. "TLSv1.2" or "TLSv1.1", and + * a desired {@link TrustManager}. + * + * + * The produced {@link SSLContext} instance will be shared with all + * the connections created by this connection factory. Use + * {@link #setSslContextFactory(SslContextFactory)} for more flexibility. + * @param protocol the TLS protocol to use. + * @param trustManager the {@link TrustManager} implementation to use. + * @see #setSslContextFactory(SslContextFactory) + * @see #useSslProtocol(SSLContext) + */ + public ConnectionFactory useSslProtocol(String protocol, TrustManager trustManager) + throws NoSuchAlgorithmException, KeyManagementException + { + SSLContext c = SSLContext.getInstance(protocol); + c.init(null, new TrustManager[] { trustManager }, null); + return useSslProtocol(c); + } + + /** + * Sets up TLS with an initialized {@link SSLContext}. The caller is responsible + * for setting up the context with a {@link TrustManager} with suitable security guarantees, + * e.g. peer verification. + * + * + * The {@link SSLContext} instance will be shared with all + * the connections created by this connection factory. Use + * {@link #setSslContextFactory(SslContextFactory)} for more flexibility. + * @param context An initialized SSLContext + * @see #setSslContextFactory(SslContextFactory) + */ + public ConnectionFactory useSslProtocol(SSLContext context) { + this.sslContextFactory = name -> context; + setSocketFactory(context.getSocketFactory()); + return this; + } + + /** + * Enable server hostname verification for TLS connections. + *

+ * This enables hostname verification regardless of the IO mode + * used (blocking or non-blocking IO). + *

+ * This can be called typically after setting the {@link SSLContext} + * with one of the useSslProtocol methods. + * + * @see NioParams#enableHostnameVerification() + * @see NioParams#setSslEngineConfigurator(SslEngineConfigurator) + * @see SslEngineConfigurators#ENABLE_HOSTNAME_VERIFICATION + * @see SocketConfigurators#ENABLE_HOSTNAME_VERIFICATION + * @see ConnectionFactory#useSslProtocol(String) + * @see ConnectionFactory#useSslProtocol(SSLContext) + * @see ConnectionFactory#useSslProtocol() + * @see ConnectionFactory#useSslProtocol(String, TrustManager) + * @since 5.4.0 + */ + public ConnectionFactory enableHostnameVerification() { + enableHostnameVerificationForNio(); + enableHostnameVerificationForBlockingIo(); + return this; + } + + protected void enableHostnameVerificationForNio() { + if (this.nioParams == null) { + this.nioParams = new NioParams(); + } + this.nioParams = this.nioParams.enableHostnameVerification(); + } + + protected void enableHostnameVerificationForBlockingIo() { + if (this.socketConf == null) { + this.socketConf = SocketConfigurators.builder().defaultConfigurator().enableHostnameVerification().build(); + } else { + this.socketConf = this.socketConf.andThen(SocketConfigurators.enableHostnameVerification()); + } + } + + public static String computeDefaultTlsProtocol(String[] supportedProtocols) { + if(supportedProtocols != null) { + for (String supportedProtocol : supportedProtocols) { + if(PREFERRED_TLS_PROTOCOL.equalsIgnoreCase(supportedProtocol)) { + return supportedProtocol; + } + } + } + return FALLBACK_TLS_PROTOCOL; + } + + /** + * Returns true if automatic connection recovery + * is enabled, false otherwise + * @return true if automatic connection recovery is enabled, false otherwise + * @see Automatic Recovery + */ + public boolean isAutomaticRecoveryEnabled() { + return automaticRecovery; + } + + /** + * Enables or disables automatic connection recovery. + * @param automaticRecovery if true, enables connection recovery + * @see Automatic Recovery + */ + public ConnectionFactory setAutomaticRecoveryEnabled(boolean automaticRecovery) { + this.automaticRecovery = automaticRecovery; + return this; + } + + /** + * Returns true if topology recovery is enabled, false otherwise + * @return true if topology recovery is enabled, false otherwise + * @see Automatic Recovery + */ + public boolean isTopologyRecoveryEnabled() { + return topologyRecovery; + } + + /** + * Enables or disables topology recovery + * @param topologyRecovery if true, enables topology recovery + * @see Automatic Recovery + */ + public ConnectionFactory setTopologyRecoveryEnabled(boolean topologyRecovery) { + this.topologyRecovery = topologyRecovery; + return this; + } + + /** + * Get the executor to use for parallel topology recovery. If null (the default), recovery is done single threaded on the main connection thread. + * @return thread pool executor + * @since 4.7.0 + */ + public ExecutorService getTopologyRecoveryExecutor() { + return topologyRecoveryExecutor; + } + + /** + * Set the executor to use for parallel topology recovery. If null (the default), recovery is done single threaded on the main connection thread. + * It is recommended to pass a ThreadPoolExecutor that will allow its core threads to timeout so these threads can die when recovery is complete. + * It's developer's responsibility to shut down the executor when it is no longer needed. + * Note: your {@link ExceptionHandler#handleTopologyRecoveryException(Connection, Channel, TopologyRecoveryException)} method should be thread-safe. + * @param topologyRecoveryExecutor thread pool executor + * @since 4.7.0 + */ + public ConnectionFactory setTopologyRecoveryExecutor(final ExecutorService topologyRecoveryExecutor) { + this.topologyRecoveryExecutor = topologyRecoveryExecutor; + return this; + } + + public ConnectionFactory setMetricsCollector(MetricsCollector metricsCollector) { + this.metricsCollector = metricsCollector; + return this; + } + + public MetricsCollector getMetricsCollector() { + return metricsCollector; + } + + /** + * Set observation collector. + * + * @param observationCollector the collector instance + * @since 5.19.0 + * @see ObservationCollector + * @see com.rabbitmq.client.observation.micrometer.MicrometerObservationCollectorBuilder + */ + public void setObservationCollector(ObservationCollector observationCollector) { + this.observationCollector = observationCollector; + } + + /** + * Set a {@link CredentialsRefreshService} instance to handle credentials refresh if appropriate. + *

+ * Each created connection will register to the refresh service to send an AMQP update.secret + * frame when credentials are about to expire. This is the refresh service responsibility to schedule + * credentials refresh and udpate.secret frame sending, based on the information provided + * by the {@link CredentialsProvider}. + *

+ * Note the {@link CredentialsRefreshService} is used only when the {@link CredentialsProvider} + * signals credentials can expire, by returning a non-null value from {@link CredentialsProvider#getTimeBeforeExpiration()}. + * + * @param credentialsRefreshService the refresh service to use + * @see #setCredentialsProvider(CredentialsProvider) + * @see DefaultCredentialsRefreshService + */ + public ConnectionFactory setCredentialsRefreshService(CredentialsRefreshService credentialsRefreshService) { + this.credentialsRefreshService = credentialsRefreshService; + return this; + } + + protected synchronized FrameHandlerFactory createFrameHandlerFactory() throws IOException { + if(nio) { + if(this.frameHandlerFactory == null) { + if(this.nioParams.getNioExecutor() == null && this.nioParams.getThreadFactory() == null) { + this.nioParams.setThreadFactory(getThreadFactory()); + } + this.frameHandlerFactory = new SocketChannelFrameHandlerFactory( + connectionTimeout, nioParams, isSSL(), sslContextFactory, + this.maxInboundMessageBodySize); + } + return this.frameHandlerFactory; + } else { + return new SocketFrameHandlerFactory(connectionTimeout, socketFactory, + socketConf, isSSL(), this.shutdownExecutor, sslContextFactory, + this.maxInboundMessageBodySize); + } + + } + + /** + * Create a new broker connection, picking the first available address from + * the list. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Future + * reconnection attempts will pick a random accessible address from the provided list. + * + * @param addrs an array of known broker addresses (hostname/port pairs) to try in order + * @return an interface to the connection + * @throws IOException if it encounters a problem + */ + public Connection newConnection(Address[] addrs) throws IOException, TimeoutException { + return newConnection(this.sharedExecutor, Arrays.asList(addrs), null); + } + + /** + * Create a new broker connection, picking the first available address from + * the list provided by the {@link AddressResolver}. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Future + * reconnection attempts will pick a random accessible address provided by the {@link AddressResolver}. + * + * @param addressResolver discovery service to list potential addresses (hostname/port pairs) to connect to + * @return an interface to the connection + * @throws IOException if it encounters a problem + * @see Automatic Recovery + */ + public Connection newConnection(AddressResolver addressResolver) throws IOException, TimeoutException { + return newConnection(this.sharedExecutor, addressResolver, null); + } + + + /** + * Create a new broker connection with a client-provided name, picking the first available address from + * the list. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Future + * reconnection attempts will pick a random accessible address from the provided list. + * + * @param addrs an array of known broker addresses (hostname/port pairs) to try in order + * @param clientProvidedName application-specific connection name, will be displayed + * in the management UI if RabbitMQ server supports it. + * This value doesn't have to be unique and cannot be used + * as a connection identifier e.g. in HTTP API requests. + * This value is supposed to be human-readable. + * @return an interface to the connection + * @throws IOException if it encounters a problem + */ + public Connection newConnection(Address[] addrs, String clientProvidedName) throws IOException, TimeoutException { + return newConnection(this.sharedExecutor, Arrays.asList(addrs), clientProvidedName); + } + + /** + * Create a new broker connection, picking the first available address from + * the list. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Future + * reconnection attempts will pick a random accessible address from the provided list. + * + * @param addrs a List of known broker addresses (hostname/port pairs) to try in order + * @return an interface to the connection + * @throws IOException if it encounters a problem + */ + public Connection newConnection(List

addrs) throws IOException, TimeoutException { + return newConnection(this.sharedExecutor, addrs, null); + } + + /** + * Create a new broker connection with a client-provided name, picking the first available address from + * the list. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Future + * reconnection attempts will pick a random accessible address from the provided list. + * + * @param addrs a List of known broker addresses (hostname/port pairs) to try in order + * @param clientProvidedName application-specific connection name, will be displayed + * in the management UI if RabbitMQ server supports it. + * This value doesn't have to be unique and cannot be used + * as a connection identifier e.g. in HTTP API requests. + * This value is supposed to be human-readable. + * @return an interface to the connection + * @throws IOException if it encounters a problem + */ + public Connection newConnection(List
addrs, String clientProvidedName) throws IOException, TimeoutException { + return newConnection(this.sharedExecutor, addrs, clientProvidedName); + } + + /** + * Create a new broker connection, picking the first available address from + * the list. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Future + * reconnection attempts will pick a random accessible address from the provided list. + * + * @param executor thread execution service for consumers on the connection + * @param addrs an array of known broker addresses (hostname/port pairs) to try in order + * @return an interface to the connection + * @throws java.io.IOException if it encounters a problem + * @see Automatic Recovery + */ + public Connection newConnection(ExecutorService executor, Address[] addrs) throws IOException, TimeoutException { + return newConnection(executor, Arrays.asList(addrs), null); + } + + + /** + * Create a new broker connection with a client-provided name, picking the first available address from + * the list. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Future + * reconnection attempts will pick a random accessible address from the provided list. + * + * @param executor thread execution service for consumers on the connection + * @param addrs an array of known broker addresses (hostname/port pairs) to try in order + * @param clientProvidedName application-specific connection name, will be displayed + * in the management UI if RabbitMQ server supports it. + * This value doesn't have to be unique and cannot be used + * as a connection identifier e.g. in HTTP API requests. + * This value is supposed to be human-readable. + * @return an interface to the connection + * @throws java.io.IOException if it encounters a problem + * @see Automatic Recovery + */ + public Connection newConnection(ExecutorService executor, Address[] addrs, String clientProvidedName) throws IOException, TimeoutException { + return newConnection(executor, Arrays.asList(addrs), clientProvidedName); + } + + /** + * Create a new broker connection, picking the first available address from + * the list. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Future + * reconnection attempts will pick a random accessible address from the provided list. + * + * @param executor thread execution service for consumers on the connection + * @param addrs a List of known broker addrs (hostname/port pairs) to try in order + * @return an interface to the connection + * @throws java.io.IOException if it encounters a problem + * @see Automatic Recovery + */ + public Connection newConnection(ExecutorService executor, List
addrs) throws IOException, TimeoutException { + return newConnection(executor, addrs, null); + } + + /** + * Create a new broker connection, picking the first available address from + * the list provided by the {@link AddressResolver}. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Future + * reconnection attempts will pick a random accessible address provided by the {@link AddressResolver}. + * + * @param executor thread execution service for consumers on the connection + * @param addressResolver discovery service to list potential addresses (hostname/port pairs) to connect to + * @return an interface to the connection + * @throws java.io.IOException if it encounters a problem + * @see Automatic Recovery + */ + public Connection newConnection(ExecutorService executor, AddressResolver addressResolver) throws IOException, TimeoutException { + return newConnection(executor, addressResolver, null); + } + + /** + * Create a new broker connection with a client-provided name, picking the first available address from + * the list. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Future + * reconnection attempts will pick a random accessible address from the provided list. + * + * @param executor thread execution service for consumers on the connection + * @param addrs a List of known broker addrs (hostname/port pairs) to try in order + * @param clientProvidedName application-specific connection name, will be displayed + * in the management UI if RabbitMQ server supports it. + * This value doesn't have to be unique and cannot be used + * as a connection identifier e.g. in HTTP API requests. + * This value is supposed to be human-readable. + * @return an interface to the connection + * @throws java.io.IOException if it encounters a problem + * @see Automatic Recovery + */ + public Connection newConnection(ExecutorService executor, List
addrs, String clientProvidedName) + throws IOException, TimeoutException { + return newConnection(executor, createAddressResolver(addrs), clientProvidedName); + } + + /** + * Create a new broker connection with a client-provided name, picking the first available address from + * the list provided by the {@link AddressResolver}. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Future + * reconnection attempts will pick a random accessible address provided by the {@link AddressResolver}. + * + * @param executor thread execution service for consumers on the connection + * @param addressResolver discovery service to list potential addresses (hostname/port pairs) to connect to + * @param clientProvidedName application-specific connection name, will be displayed + * in the management UI if RabbitMQ server supports it. + * This value doesn't have to be unique and cannot be used + * as a connection identifier e.g. in HTTP API requests. + * This value is supposed to be human-readable. + * @return an interface to the connection + * @throws java.io.IOException if it encounters a problem + * @see Automatic Recovery + */ + public Connection newConnection(ExecutorService executor, AddressResolver addressResolver, String clientProvidedName) + throws IOException, TimeoutException { + if(this.metricsCollector == null) { + this.metricsCollector = new NoOpMetricsCollector(); + } + // make sure we respect the provided thread factory + FrameHandlerFactory fhFactory = createFrameHandlerFactory(); + ConnectionParams params = params(executor); + // set client-provided via a client property + if (clientProvidedName != null) { + Map properties = new HashMap(params.getClientProperties()); + properties.put("connection_name", clientProvidedName); + params.setClientProperties(properties); + } + + if (isAutomaticRecoveryEnabled()) { + // see com.rabbitmq.client.impl.recovery.RecoveryAwareAMQConnectionFactory#newConnection + // No Sonar: no need to close this resource because we're the one that creates it + // and hands it over to the user + AutorecoveringConnection conn = new AutorecoveringConnection( + params, fhFactory, addressResolver, metricsCollector, observationCollector); //NOSONAR + + conn.init(); + return conn; + } else { + List
addrs = addressResolver.getAddresses(); + Exception lastException = null; + for (Address addr : addrs) { + try { + FrameHandler handler = fhFactory.create(addr, clientProvidedName); + AMQConnection conn = createConnection(params, handler, metricsCollector); + conn.start(); + this.metricsCollector.newConnection(conn); + return conn; + } catch (IOException e) { + lastException = e; + } catch (TimeoutException te) { + lastException = te; + } + } + if (lastException != null) { + if (lastException instanceof IOException) { + throw (IOException) lastException; + } else if (lastException instanceof TimeoutException) { + throw (TimeoutException) lastException; + } + } + throw new IOException("failed to connect"); + } + } + + public ConnectionParams params(ExecutorService consumerWorkServiceExecutor) { + ConnectionParams result = new ConnectionParams(); + + result.setCredentialsProvider(credentialsProvider); + result.setConsumerWorkServiceExecutor(consumerWorkServiceExecutor); + result.setVirtualHost(virtualHost); + result.setClientProperties(getClientProperties()); + result.setRequestedFrameMax(requestedFrameMax); + result.setRequestedChannelMax(requestedChannelMax); + result.setShutdownTimeout(shutdownTimeout); + result.setSaslConfig(saslConfig); + result.setNetworkRecoveryInterval(networkRecoveryInterval); + result.setRecoveryDelayHandler(recoveryDelayHandler); + result.setTopologyRecovery(topologyRecovery); + result.setTopologyRecoveryExecutor(topologyRecoveryExecutor); + result.setExceptionHandler(exceptionHandler); + result.setThreadFactory(threadFactory); + result.setHandshakeTimeout(handshakeTimeout); + result.setRequestedHeartbeat(requestedHeartbeat); + result.setShutdownExecutor(shutdownExecutor); + result.setHeartbeatExecutor(heartbeatExecutor); + result.setChannelRpcTimeout(channelRpcTimeout); + result.setChannelShouldCheckRpcResponseType(channelShouldCheckRpcResponseType); + result.setWorkPoolTimeout(workPoolTimeout); + result.setErrorOnWriteListener(errorOnWriteListener); + result.setTopologyRecoveryFilter(topologyRecoveryFilter); + result.setConnectionRecoveryTriggeringCondition(connectionRecoveryTriggeringCondition); + result.setTopologyRecoveryRetryHandler(topologyRecoveryRetryHandler); + result.setRecoveredQueueNameSupplier(recoveredQueueNameSupplier); + result.setTrafficListener(trafficListener); + result.setCredentialsRefreshService(credentialsRefreshService); + result.setMaxInboundMessageBodySize(maxInboundMessageBodySize); + return result; + } + + protected AMQConnection createConnection(ConnectionParams params, FrameHandler frameHandler, MetricsCollector metricsCollector) { + return new AMQConnection(params, frameHandler, metricsCollector, observationCollector); + } + + /** + * Create a new broker connection. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Reconnection + * attempts will always use the address configured on {@link ConnectionFactory}. + * + * @return an interface to the connection + * @throws IOException if it encounters a problem + */ + public Connection newConnection() throws IOException, TimeoutException { + return newConnection(this.sharedExecutor, Collections.singletonList(new Address(getHost(), getPort()))); + } + + /** + * Create a new broker connection. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Reconnection + * attempts will always use the address configured on {@link ConnectionFactory}. + * + * @param connectionName client-provided connection name (an arbitrary string). Will + * be displayed in management UI if the server supports it. + * @return an interface to the connection + * @throws IOException if it encounters a problem + */ + public Connection newConnection(String connectionName) throws IOException, TimeoutException { + return newConnection(this.sharedExecutor, Collections.singletonList(new Address(getHost(), getPort())), connectionName); + } + + /** + * Create a new broker connection. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Reconnection + * attempts will always use the address configured on {@link ConnectionFactory}. + * + * @param executor thread execution service for consumers on the connection + * @return an interface to the connection + * @throws IOException if it encounters a problem + */ + public Connection newConnection(ExecutorService executor) throws IOException, TimeoutException { + return newConnection(executor, Collections.singletonList(new Address(getHost(), getPort()))); + } + + /** + * Create a new broker connection. + * + * If automatic connection recovery + * is enabled, the connection returned by this method will be {@link Recoverable}. Reconnection + * attempts will always use the address configured on {@link ConnectionFactory}. + * + * @param executor thread execution service for consumers on the connection + * @param connectionName client-provided connection name (an arbitrary string). Will + * be displayed in management UI if the server supports it. + * @return an interface to the connection + * @throws IOException if it encounters a problem + */ + public Connection newConnection(ExecutorService executor, String connectionName) throws IOException, TimeoutException { + return newConnection(executor, Collections.singletonList(new Address(getHost(), getPort())), connectionName); + } + + protected AddressResolver createAddressResolver(List
addresses) { + if (addresses == null || addresses.isEmpty()) { + throw new IllegalArgumentException("Please provide at least one address to connect to"); + } else if (addresses.size() > 1) { + return new ListAddressResolver(addresses); + } else { + return new DnsRecordIpAddressResolver(addresses.get(0), isSSL()); + } + } + + @Override public ConnectionFactory clone(){ + try { + ConnectionFactory clone = (ConnectionFactory)super.clone(); + return clone; + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + /** + * Load settings from a property file. + * Keys must be prefixed with rabbitmq., + * use {@link ConnectionFactory#load(String, String)} to + * specify your own prefix. + * @param propertyFileLocation location of the property file to use + * @throws IOException when something goes wrong reading the file + * @since 4.4.0 + * @see ConnectionFactoryConfigurator + */ + public ConnectionFactory load(String propertyFileLocation) throws IOException { + ConnectionFactoryConfigurator.load(this, propertyFileLocation); + return this; + } + + /** + * Load settings from a property file. + * @param propertyFileLocation location of the property file to use + * @param prefix key prefix for the entries in the file + * @throws IOException when something goes wrong reading the file + * @since 4.4.0 + * @see ConnectionFactoryConfigurator + */ + public ConnectionFactory load(String propertyFileLocation, String prefix) throws IOException { + ConnectionFactoryConfigurator.load(this, propertyFileLocation, prefix); + return this; + } + + /** + * Load settings from a {@link Properties} instance. + * Keys must be prefixed with rabbitmq., + * use {@link ConnectionFactory#load(Properties, String)} to + * specify your own prefix. + * @param properties source for settings + * @since 4.4.0 + * @see ConnectionFactoryConfigurator + */ + public ConnectionFactory load(Properties properties) { + ConnectionFactoryConfigurator.load(this, properties); + return this; + } + + /** + * Load settings from a {@link Properties} instance. + * @param properties source for settings + * @param prefix key prefix for properties entries + * @since 4.4.0 + * @see ConnectionFactoryConfigurator + */ + @SuppressWarnings("unchecked") + public ConnectionFactory load(Properties properties, String prefix) { + ConnectionFactoryConfigurator.load(this, (Map) properties, prefix); + return this; + } + + /** + * Load settings from a {@link Map} instance. + * Keys must be prefixed with rabbitmq., + * use {@link ConnectionFactory#load(Map, String)} to + * specify your own prefix. + * @param properties source for settings + * @since 4.4.0 + * @see ConnectionFactoryConfigurator + */ + public ConnectionFactory load(Map properties) { + ConnectionFactoryConfigurator.load(this, properties); + return this; + } + + /** + * Load settings from a {@link Map} instance. + * @param properties source for settings + * @param prefix key prefix for map entries + * @since 4.4.0 + * @see ConnectionFactoryConfigurator + */ + public ConnectionFactory load(Map properties, String prefix) { + ConnectionFactoryConfigurator.load(this, properties, prefix); + return this; + } + + /** + * Returns automatic connection recovery interval in milliseconds. + * @return how long will automatic recovery wait before attempting to reconnect, in ms; default is 5000 + */ + public long getNetworkRecoveryInterval() { + return networkRecoveryInterval; + } + + /** + * Sets connection recovery interval. Default is 5000. + * Uses {@link com.rabbitmq.client.RecoveryDelayHandler.DefaultRecoveryDelayHandler} by default. + * Use another {@link RecoveryDelayHandler} implementation for more flexibility. + * @param networkRecoveryInterval how long will automatic recovery wait before attempting to reconnect, in ms + * @see RecoveryDelayHandler + */ + public ConnectionFactory setNetworkRecoveryInterval(int networkRecoveryInterval) { + this.networkRecoveryInterval = networkRecoveryInterval; + return this; + } + + /** + * Sets connection recovery interval. Default is 5000. + * Uses {@link com.rabbitmq.client.RecoveryDelayHandler.DefaultRecoveryDelayHandler} by default. + * Use another {@link RecoveryDelayHandler} implementation for more flexibility. + * @param networkRecoveryInterval how long will automatic recovery wait before attempting to reconnect, in ms + * @see RecoveryDelayHandler + */ + public ConnectionFactory setNetworkRecoveryInterval(long networkRecoveryInterval) { + this.networkRecoveryInterval = networkRecoveryInterval; + return this; + } + + /** + * Returns automatic connection recovery delay handler. + * @return recovery delay handler. May be null if not set. + * @since 4.3.0 + */ + public RecoveryDelayHandler getRecoveryDelayHandler() { + return recoveryDelayHandler; + } + + /** + * Sets the automatic connection recovery delay handler. + * @param recoveryDelayHandler the recovery delay handler + * @since 4.3.0 + */ + public ConnectionFactory setRecoveryDelayHandler(final RecoveryDelayHandler recoveryDelayHandler) { + this.recoveryDelayHandler = recoveryDelayHandler; + return this; + } + + /** + * Sets the parameters when using NIO. + * + * + * @param nioParams + * @see NioParams + */ + public ConnectionFactory setNioParams(NioParams nioParams) { + this.nioParams = nioParams; + return this; + } + + /** + * Retrieve the parameters for NIO mode. + * @return + */ + public NioParams getNioParams() { + return nioParams; + } + + /** + * Use non-blocking IO (NIO) for communication with the server. + * With NIO, several connections created from the same {@link ConnectionFactory} + * can use the same IO thread. + * + * A client process using a lot of not-so-active connections can benefit + * from NIO, as it would use fewer threads than with the traditional, blocking IO mode. + * + * Use {@link NioParams} to tune NIO and a {@link SocketChannelConfigurator} to + * configure the underlying {@link java.nio.channels.SocketChannel}s for connections. + * + * @see NioParams + * @see SocketChannelConfigurator + * @see java.nio.channels.SocketChannel + * @see java.nio.channels.Selector + */ + public ConnectionFactory useNio() { + this.nio = true; + return this; + } + + /** + * Use blocking IO for communication with the server. + * With blocking IO, each connection creates its own thread + * to read data from the server. + */ + public ConnectionFactory useBlockingIo() { + this.nio = false; + return this; + } + + /** + * Set the continuation timeout for RPC calls in channels. + * Default is 10 minutes. 0 means no timeout. + * @param channelRpcTimeout + */ + public ConnectionFactory setChannelRpcTimeout(int channelRpcTimeout) { + if(channelRpcTimeout < 0) { + throw new IllegalArgumentException("Timeout cannot be less than 0"); + } + this.channelRpcTimeout = channelRpcTimeout; + return this; + } + + /** + * Get the timeout for RPC calls in channels. + * @return + */ + public int getChannelRpcTimeout() { + return channelRpcTimeout; + } + + /** + * Maximum body size of inbound (received) messages in bytes. + * + *

Default value is 67,108,864 (64 MiB). + * + * @param maxInboundMessageBodySize the maximum size of inbound messages + */ + public void setMaxInboundMessageBodySize(int maxInboundMessageBodySize) { + if (maxInboundMessageBodySize <= 0) { + throw new IllegalArgumentException("Max inbound message body size must be greater than 0: " + + maxInboundMessageBodySize); + } + this.maxInboundMessageBodySize = maxInboundMessageBodySize; + } + + /** + * The factory to create SSL contexts. + * This provides more flexibility to create {@link SSLContext}s + * for different connections than sharing the {@link SSLContext} + * with all the connections produced by the connection factory + * (which is the case with the {@link #useSslProtocol()} methods). + * This way, different connections with a different certificate + * for each of them is a possible scenario. + * @param sslContextFactory + * @see #useSslProtocol(SSLContext) + * @since 5.0.0 + */ + public ConnectionFactory setSslContextFactory(SslContextFactory sslContextFactory) { + this.sslContextFactory = sslContextFactory; + return this; + } + + /** + * When set to true, channels will check the response type (e.g. queue.declare + * expects a queue.declare-ok response) of RPC calls + * and ignore those that do not match. + * Default is false. + * @param channelShouldCheckRpcResponseType + */ + public ConnectionFactory setChannelShouldCheckRpcResponseType(boolean channelShouldCheckRpcResponseType) { + this.channelShouldCheckRpcResponseType = channelShouldCheckRpcResponseType; + return this; + } + + public boolean isChannelShouldCheckRpcResponseType() { + return channelShouldCheckRpcResponseType; + } + + /** + * Timeout (in ms) for work pool enqueueing. + * The {@link com.rabbitmq.client.impl.WorkPool} dispatches several types of responses + * from the broker (e.g. deliveries). A high-traffic + * client with slow consumers can exhaust the work pool and + * compromise the whole connection (by e.g. letting the broker + * saturate the receive TCP buffers). Setting a timeout + * would make the connection fail early and avoid hard-to-diagnose + * TCP connection failure. Note this shouldn't happen + * with clients that set appropriate QoS values. + * Default is no timeout. + * + * @param workPoolTimeout timeout in ms + * @since 4.5.0 + */ + public ConnectionFactory setWorkPoolTimeout(int workPoolTimeout) { + this.workPoolTimeout = workPoolTimeout; + return this; + } + + public int getWorkPoolTimeout() { + return workPoolTimeout; + } + + /** + * Set a listener to be called when connection gets an IO error trying to write on the socket. + * Default listener triggers connection recovery asynchronously and propagates + * the exception. Override the default listener to disable or + * customise automatic connection triggering on write operations. + * + * @param errorOnWriteListener the listener + * @since 4.5.0 + */ + public ConnectionFactory setErrorOnWriteListener(ErrorOnWriteListener errorOnWriteListener) { + this.errorOnWriteListener = errorOnWriteListener; + return this; + } + + /** + * Set filter to include/exclude entities from topology recovery. + * + * @since 4.8.0 + */ + public ConnectionFactory setTopologyRecoveryFilter(TopologyRecoveryFilter topologyRecoveryFilter) { + this.topologyRecoveryFilter = topologyRecoveryFilter; + return this; + } + + /** + * Allows to decide on automatic connection recovery is triggered. + * Default is for shutdown not initiated by application or missed heartbeat errors. + * + * @param connectionRecoveryTriggeringCondition + */ + public ConnectionFactory setConnectionRecoveryTriggeringCondition(Predicate connectionRecoveryTriggeringCondition) { + this.connectionRecoveryTriggeringCondition = connectionRecoveryTriggeringCondition; + return this; + } + + /** + * Set retry handler for topology recovery. + * Default is no retry. + * + * @param topologyRecoveryRetryHandler + * @since 5.4.0 + */ + public ConnectionFactory setTopologyRecoveryRetryHandler(RetryHandler topologyRecoveryRetryHandler) { + this.topologyRecoveryRetryHandler = topologyRecoveryRetryHandler; + return this; + } + + /** + * Set the recovered queue name supplier. Default is use the same queue name when recovering queues. + * + * @param recoveredQueueNameSupplier queue name supplier + */ + public ConnectionFactory setRecoveredQueueNameSupplier(RecoveredQueueNameSupplier recoveredQueueNameSupplier) { + this.recoveredQueueNameSupplier = recoveredQueueNameSupplier; + return this; + } + + /** + * Traffic listener notified of inbound and outbound {@link Command}s. + *

+ * Useful for debugging purposes, e.g. logging all sent and received messages. + * Default is no-op. + * + * @param trafficListener + * @see TrafficListener + * @see com.rabbitmq.client.impl.LogTrafficListener + * @since 5.5.0 + */ + public ConnectionFactory setTrafficListener(TrafficListener trafficListener) { + this.trafficListener = trafficListener; + return this; + } +} diff --git a/src/main/java/com/rabbitmq/client/ConnectionFactoryConfigurator.java b/src/main/java/com/rabbitmq/client/ConnectionFactoryConfigurator.java new file mode 100644 index 0000000000..12ebcadbbc --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ConnectionFactoryConfigurator.java @@ -0,0 +1,404 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.nio.NioParams; + +import javax.net.ssl.*; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.security.*; +import java.security.cert.CertificateException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Helper class to load {@link ConnectionFactory} settings from a property file. + *

+ * The authorised keys are the constants values in this class (e.g. USERNAME). + * The property file/properties instance/map instance keys can have + * a prefix, the default being rabbitmq.. + *

+ * Property files can be loaded from the file system (the default), + * but also from the classpath, by using the classpath: prefix + * in the location. + *

+ * Client properties can be set by using + * the client.properties. prefix, e.g. client.properties.app.name. + * Default client properties and custom client properties are merged. To remove + * a default client property, set its key to an empty value. + * + * @see ConnectionFactory#load(String, String) + * @since 5.1.0 + */ +public class ConnectionFactoryConfigurator { + + public static final String DEFAULT_PREFIX = "rabbitmq."; + + public static final String USERNAME = "username"; + public static final String PASSWORD = "password"; //NOSONAR + public static final String VIRTUAL_HOST = "virtual.host"; + public static final String HOST = "host"; + public static final String PORT = "port"; + public static final String CONNECTION_CHANNEL_MAX = "connection.channel.max"; + public static final String CONNECTION_FRAME_MAX = "connection.frame.max"; + public static final String CONNECTION_HEARTBEAT = "connection.heartbeat"; + public static final String CONNECTION_TIMEOUT = "connection.timeout"; + public static final String HANDSHAKE_TIMEOUT = "handshake.timeout"; + public static final String SHUTDOWN_TIMEOUT = "shutdown.timeout"; + public static final String CLIENT_PROPERTIES_PREFIX = "client.properties."; + public static final String CONNECTION_RECOVERY_ENABLED = "connection.recovery.enabled"; + public static final String TOPOLOGY_RECOVERY_ENABLED = "topology.recovery.enabled"; + public static final String CONNECTION_RECOVERY_INTERVAL = "connection.recovery.interval"; + public static final String CHANNEL_RPC_TIMEOUT = "channel.rpc.timeout"; + public static final String CHANNEL_SHOULD_CHECK_RPC_RESPONSE_TYPE = "channel.should.check.rpc.response.type"; + public static final String USE_NIO = "use.nio"; + public static final String NIO_READ_BYTE_BUFFER_SIZE = "nio.read.byte.buffer.size"; + public static final String NIO_WRITE_BYTE_BUFFER_SIZE = "nio.write.byte.buffer.size"; + public static final String NIO_NB_IO_THREADS = "nio.nb.io.threads"; + public static final String NIO_WRITE_ENQUEUING_TIMEOUT_IN_MS = "nio.write.enqueuing.timeout.in.ms"; + public static final String NIO_WRITE_QUEUE_CAPACITY = "nio.write.queue.capacity"; + public static final String SSL_ALGORITHM = "ssl.algorithm"; + public static final String SSL_ENABLED = "ssl.enabled"; + public static final String SSL_KEY_STORE = "ssl.key.store"; + public static final String SSL_KEY_STORE_PASSWORD = "ssl.key.store.password"; + public static final String SSL_KEY_STORE_TYPE = "ssl.key.store.type"; + public static final String SSL_KEY_STORE_ALGORITHM = "ssl.key.store.algorithm"; + public static final String SSL_TRUST_STORE = "ssl.trust.store"; + public static final String SSL_TRUST_STORE_PASSWORD = "ssl.trust.store.password"; + public static final String SSL_TRUST_STORE_TYPE = "ssl.trust.store.type"; + public static final String SSL_TRUST_STORE_ALGORITHM = "ssl.trust.store.algorithm"; + public static final String SSL_VALIDATE_SERVER_CERTIFICATE = "ssl.validate.server.certificate"; + public static final String SSL_VERIFY_HOSTNAME = "ssl.verify.hostname"; + + // aliases allow to be compatible with keys from Spring Boot and still be consistent with + // the initial naming of the keys + private static final Map> ALIASES = new ConcurrentHashMap>() {{ + put(SSL_KEY_STORE, Arrays.asList("ssl.key-store")); + put(SSL_KEY_STORE_PASSWORD, Arrays.asList("ssl.key-store-password")); + put(SSL_KEY_STORE_TYPE, Arrays.asList("ssl.key-store-type")); + put(SSL_KEY_STORE_ALGORITHM, Arrays.asList("ssl.key-store-algorithm")); + put(SSL_TRUST_STORE, Arrays.asList("ssl.trust-store")); + put(SSL_TRUST_STORE_PASSWORD, Arrays.asList("ssl.trust-store-password")); + put(SSL_TRUST_STORE_TYPE, Arrays.asList("ssl.trust-store-type")); + put(SSL_TRUST_STORE_ALGORITHM, Arrays.asList("ssl.trust-store-algorithm")); + put(SSL_VALIDATE_SERVER_CERTIFICATE, Arrays.asList("ssl.validate-server-certificate")); + put(SSL_VERIFY_HOSTNAME, Arrays.asList("ssl.verify-hostname")); + }}; + + @SuppressWarnings("unchecked") + public static void load(ConnectionFactory cf, String propertyFileLocation, String prefix) throws IOException { + if (propertyFileLocation == null || propertyFileLocation.isEmpty()) { + throw new IllegalArgumentException("Property file argument cannot be null or empty"); + } + Properties properties = new Properties(); + try (InputStream in = loadResource(propertyFileLocation)) { + properties.load(in); + } + load(cf, (Map) properties, prefix); + } + + private static InputStream loadResource(String location) throws FileNotFoundException { + if (location.startsWith("classpath:")) { + return ConnectionFactoryConfigurator.class.getResourceAsStream( + location.substring("classpath:".length()) + ); + } else { + return new FileInputStream(location); + } + } + + public static void load(ConnectionFactory cf, Map properties, String prefix) { + prefix = prefix == null ? "" : prefix; + String uri = properties.get(prefix + "uri"); + if (uri != null) { + try { + cf.setUri(uri); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Error while setting AMQP URI: " + uri, e); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Error while setting AMQP URI: " + uri, e); + } catch (KeyManagementException e) { + throw new IllegalArgumentException("Error while setting AMQP URI: " + uri, e); + } + } + String username = lookUp(USERNAME, properties, prefix); + if (username != null) { + cf.setUsername(username); + } + String password = lookUp(PASSWORD, properties, prefix); + if (password != null) { + cf.setPassword(password); + } + String vhost = lookUp(VIRTUAL_HOST, properties, prefix); + if (vhost != null) { + cf.setVirtualHost(vhost); + } + String host = lookUp(HOST, properties, prefix); + if (host != null) { + cf.setHost(host); + } + String port = lookUp(PORT, properties, prefix); + if (port != null) { + cf.setPort(Integer.valueOf(port)); + } + String requestedChannelMax = lookUp(CONNECTION_CHANNEL_MAX, properties, prefix); + if (requestedChannelMax != null) { + cf.setRequestedChannelMax(Integer.valueOf(requestedChannelMax)); + } + String requestedFrameMax = lookUp(CONNECTION_FRAME_MAX, properties, prefix); + if (requestedFrameMax != null) { + cf.setRequestedFrameMax(Integer.valueOf(requestedFrameMax)); + } + String requestedHeartbeat = lookUp(CONNECTION_HEARTBEAT, properties, prefix); + if (requestedHeartbeat != null) { + cf.setRequestedHeartbeat(Integer.valueOf(requestedHeartbeat)); + } + String connectionTimeout = lookUp(CONNECTION_TIMEOUT, properties, prefix); + if (connectionTimeout != null) { + cf.setConnectionTimeout(Integer.valueOf(connectionTimeout)); + } + String handshakeTimeout = lookUp(HANDSHAKE_TIMEOUT, properties, prefix); + if (handshakeTimeout != null) { + cf.setHandshakeTimeout(Integer.valueOf(handshakeTimeout)); + } + String shutdownTimeout = lookUp(SHUTDOWN_TIMEOUT, properties, prefix); + if (shutdownTimeout != null) { + cf.setShutdownTimeout(Integer.valueOf(shutdownTimeout)); + } + + Map clientProperties = new HashMap(); + Map defaultClientProperties = AMQConnection.defaultClientProperties(); + clientProperties.putAll(defaultClientProperties); + + for (Map.Entry entry : properties.entrySet()) { + if (entry.getKey().startsWith(prefix + CLIENT_PROPERTIES_PREFIX)) { + String clientPropertyKey = entry.getKey().substring((prefix + CLIENT_PROPERTIES_PREFIX).length()); + if (defaultClientProperties.containsKey(clientPropertyKey) && (entry.getValue() == null || entry.getValue().trim().isEmpty())) { + // if default property and value is empty, remove this property + clientProperties.remove(clientPropertyKey); + } else { + clientProperties.put( + clientPropertyKey, + entry.getValue() + ); + } + } + } + cf.setClientProperties(clientProperties); + + String automaticRecovery = lookUp(CONNECTION_RECOVERY_ENABLED, properties, prefix); + if (automaticRecovery != null) { + cf.setAutomaticRecoveryEnabled(Boolean.valueOf(automaticRecovery)); + } + String topologyRecovery = lookUp(TOPOLOGY_RECOVERY_ENABLED, properties, prefix); + if (topologyRecovery != null) { + cf.setTopologyRecoveryEnabled(Boolean.valueOf(topologyRecovery)); + } + String networkRecoveryInterval = lookUp(CONNECTION_RECOVERY_INTERVAL, properties, prefix); + if (networkRecoveryInterval != null) { + cf.setNetworkRecoveryInterval(Long.valueOf(networkRecoveryInterval)); + } + String channelRpcTimeout = lookUp(CHANNEL_RPC_TIMEOUT, properties, prefix); + if (channelRpcTimeout != null) { + cf.setChannelRpcTimeout(Integer.valueOf(channelRpcTimeout)); + } + String channelShouldCheckRpcResponseType = lookUp(CHANNEL_SHOULD_CHECK_RPC_RESPONSE_TYPE, properties, prefix); + if (channelShouldCheckRpcResponseType != null) { + cf.setChannelShouldCheckRpcResponseType(Boolean.valueOf(channelShouldCheckRpcResponseType)); + } + + String useNio = lookUp(USE_NIO, properties, prefix); + if (useNio != null && Boolean.valueOf(useNio)) { + cf.useNio(); + + NioParams nioParams = new NioParams(); + + String readByteBufferSize = lookUp(NIO_READ_BYTE_BUFFER_SIZE, properties, prefix); + if (readByteBufferSize != null) { + nioParams.setReadByteBufferSize(Integer.valueOf(readByteBufferSize)); + } + String writeByteBufferSize = lookUp(NIO_WRITE_BYTE_BUFFER_SIZE, properties, prefix); + if (writeByteBufferSize != null) { + nioParams.setWriteByteBufferSize(Integer.valueOf(writeByteBufferSize)); + } + String nbIoThreads = lookUp(NIO_NB_IO_THREADS, properties, prefix); + if (nbIoThreads != null) { + nioParams.setNbIoThreads(Integer.valueOf(nbIoThreads)); + } + String writeEnqueuingTime = lookUp(NIO_WRITE_ENQUEUING_TIMEOUT_IN_MS, properties, prefix); + if (writeEnqueuingTime != null) { + nioParams.setWriteEnqueuingTimeoutInMs(Integer.valueOf(writeEnqueuingTime)); + } + String writeQueueCapacity = lookUp(NIO_WRITE_QUEUE_CAPACITY, properties, prefix); + if (writeQueueCapacity != null) { + nioParams.setWriteQueueCapacity(Integer.valueOf(writeQueueCapacity)); + } + cf.setNioParams(nioParams); + } + + String useSsl = lookUp(SSL_ENABLED, properties, prefix); + if (useSsl != null && Boolean.valueOf(useSsl)) { + setUpSsl(cf, properties, prefix); + } + } + + private static void setUpSsl(ConnectionFactory cf, Map properties, String prefix) { + String algorithm = lookUp(SSL_ALGORITHM, properties, prefix); + String keyStoreLocation = lookUp(SSL_KEY_STORE, properties, prefix); + String keyStorePassword = lookUp(SSL_KEY_STORE_PASSWORD, properties, prefix); + String keyStoreType = lookUp(SSL_KEY_STORE_TYPE, properties, prefix, "PKCS12"); + String keyStoreAlgorithm = lookUp(SSL_KEY_STORE_ALGORITHM, properties, prefix, "SunX509"); + String trustStoreLocation = lookUp(SSL_TRUST_STORE, properties, prefix); + String trustStorePassword = lookUp(SSL_TRUST_STORE_PASSWORD, properties, prefix); + String trustStoreType = lookUp(SSL_TRUST_STORE_TYPE, properties, prefix, "JKS"); + String trustStoreAlgorithm = lookUp(SSL_TRUST_STORE_ALGORITHM, properties, prefix, "SunX509"); + String validateServerCertificate = lookUp(SSL_VALIDATE_SERVER_CERTIFICATE, properties, prefix); + String verifyHostname = lookUp(SSL_VERIFY_HOSTNAME, properties, prefix); + + try { + algorithm = algorithm == null ? + ConnectionFactory.computeDefaultTlsProtocol(SSLContext.getDefault().getSupportedSSLParameters().getProtocols()) : algorithm; + boolean enableHostnameVerification = verifyHostname == null ? Boolean.FALSE : Boolean.valueOf(verifyHostname); + + if (keyStoreLocation == null && trustStoreLocation == null) { + setUpBasicSsl( + cf, + validateServerCertificate == null ? Boolean.FALSE : Boolean.valueOf(validateServerCertificate), + enableHostnameVerification, + algorithm + ); + } else { + KeyManager[] keyManagers = configureKeyManagers(keyStoreLocation, keyStorePassword, keyStoreType, keyStoreAlgorithm); + TrustManager[] trustManagers = configureTrustManagers(trustStoreLocation, trustStorePassword, trustStoreType, trustStoreAlgorithm); + + // create ssl context + SSLContext sslContext = SSLContext.getInstance(algorithm); + sslContext.init(keyManagers, trustManagers, null); + + cf.useSslProtocol(sslContext); + + if (enableHostnameVerification) { + cf.enableHostnameVerification(); + } + } + } catch (NoSuchAlgorithmException | IOException | CertificateException | + UnrecoverableKeyException | KeyStoreException | KeyManagementException e) { + throw new IllegalStateException("Error while configuring TLS", e); + } + } + + private static KeyManager[] configureKeyManagers(String keystore, String keystorePassword, String keystoreType, String keystoreAlgorithm) throws KeyStoreException, IOException, NoSuchAlgorithmException, + CertificateException, UnrecoverableKeyException { + char[] keyPassphrase = null; + if (keystorePassword != null) { + keyPassphrase = keystorePassword.toCharArray(); + } + KeyManager[] keyManagers = null; + if (keystore != null) { + KeyStore ks = KeyStore.getInstance(keystoreType); + try (InputStream in = loadResource(keystore)) { + ks.load(in, keyPassphrase); + } + KeyManagerFactory kmf = KeyManagerFactory.getInstance(keystoreAlgorithm); + kmf.init(ks, keyPassphrase); + keyManagers = kmf.getKeyManagers(); + } + return keyManagers; + } + + private static TrustManager[] configureTrustManagers(String truststore, String truststorePassword, String truststoreType, String truststoreAlgorithm) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + char[] trustPassphrase = null; + if (truststorePassword != null) { + trustPassphrase = truststorePassword.toCharArray(); + } + TrustManager[] trustManagers = null; + if (truststore != null) { + KeyStore tks = KeyStore.getInstance(truststoreType); + try (InputStream in = loadResource(truststore)) { + tks.load(in, trustPassphrase); + } + TrustManagerFactory tmf = TrustManagerFactory.getInstance(truststoreAlgorithm); + tmf.init(tks); + trustManagers = tmf.getTrustManagers(); + } + return trustManagers; + } + + private static void setUpBasicSsl(ConnectionFactory cf, boolean validateServerCertificate, boolean verifyHostname, String sslAlgorithm) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException { + if (validateServerCertificate) { + useDefaultTrustStore(cf, sslAlgorithm, verifyHostname); + } else { + if (sslAlgorithm == null) { + cf.useSslProtocol(); + } else { + cf.useSslProtocol(sslAlgorithm); + } + } + } + + private static void useDefaultTrustStore(ConnectionFactory cf, String sslAlgorithm, boolean verifyHostname) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + SSLContext sslContext = SSLContext.getInstance(sslAlgorithm); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init((KeyStore) null); + sslContext.init(null, trustManagerFactory.getTrustManagers(), null); + cf.useSslProtocol(sslContext); + if (verifyHostname) { + cf.enableHostnameVerification(); + } + } + + public static void load(ConnectionFactory connectionFactory, String propertyFileLocation) throws IOException { + load(connectionFactory, propertyFileLocation, DEFAULT_PREFIX); + } + + @SuppressWarnings("unchecked") + public static void load(ConnectionFactory connectionFactory, Properties properties) { + load(connectionFactory, (Map) properties, DEFAULT_PREFIX); + } + + @SuppressWarnings("unchecked") + public static void load(ConnectionFactory connectionFactory, Properties properties, String prefix) { + load(connectionFactory, (Map) properties, prefix); + } + + public static void load(ConnectionFactory connectionFactory, Map properties) { + load(connectionFactory, properties, DEFAULT_PREFIX); + } + + public static String lookUp(String key, Map properties, String prefix) { + return lookUp(key, properties, prefix, null); + } + + public static String lookUp(String key, Map properties, String prefix, String defaultValue) { + String value = properties.get(prefix + key); + if (value == null) { + value = ALIASES.getOrDefault(key, Collections.emptyList()).stream() + .map(alias -> properties.get(prefix + alias)) + .filter(v -> v != null) + .findFirst().orElse(defaultValue); + } + return value; + } + + +} diff --git a/src/com/rabbitmq/client/Consumer.java b/src/main/java/com/rabbitmq/client/Consumer.java similarity index 76% rename from src/com/rabbitmq/client/Consumer.java rename to src/main/java/com/rabbitmq/client/Consumer.java index d1c43f2c70..1de1349ed6 100644 --- a/src/com/rabbitmq/client/Consumer.java +++ b/src/main/java/com/rabbitmq/client/Consumer.java @@ -1,38 +1,41 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; import java.io.IOException; /** - * Interface for application callback objects to receive notifications and messages from + *

Interface for application callback objects to receive notifications and messages from * a queue by subscription. * Most implementations will subclass {@link DefaultConsumer}. - *

+ *

+ *

* The methods of this interface are invoked in a dispatch * thread which is separate from the {@link Connection}'s thread. This * allows {@link Consumer}s to call {@link Channel} or {@link * Connection} methods without causing a deadlock. - *

+ *

* The {@link Consumer}s on a particular {@link Channel} are invoked serially on one or more * dispatch threads. {@link Consumer}s should avoid executing long-running code * because this will delay dispatch of messages to other {@link Consumer}s on the same * {@link Channel}. * + * For a lambda-oriented syntax, use {@link DeliverCallback}, + * {@link CancelCallback}, and {@link ConsumerShutdownSignalCallback}. + * * @see Channel#basicConsume(String, boolean, String, boolean, boolean, java.util.Map, Consumer) * @see Channel#basicCancel */ diff --git a/src/main/java/com/rabbitmq/client/ConsumerCancelledException.java b/src/main/java/com/rabbitmq/client/ConsumerCancelledException.java new file mode 100644 index 0000000000..31d5dc209a --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ConsumerCancelledException.java @@ -0,0 +1,36 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import com.rabbitmq.utility.SensibleClone; + +public class ConsumerCancelledException extends RuntimeException implements + SensibleClone { + + /** Default for non-checking. */ + private static final long serialVersionUID = 1L; + + @Override + public ConsumerCancelledException sensibleClone() { + try { + return (ConsumerCancelledException) super.clone(); + } catch (CloneNotSupportedException e) { + // You've got to be kidding me + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/com/rabbitmq/client/ConsumerShutdownSignalCallback.java b/src/main/java/com/rabbitmq/client/ConsumerShutdownSignalCallback.java new file mode 100644 index 0000000000..c6a23a4bdb --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ConsumerShutdownSignalCallback.java @@ -0,0 +1,42 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.util.Map; + +/** + * Callback interface to be notified when either the consumer channel + * or the underlying connection has been shut down. + * Prefer it over {@link Consumer} for a lambda-oriented syntax, + * if you don't need to implement all the application callbacks. + * @see CancelCallback + * @see DeliverCallback + * @see Channel#basicConsume(String, boolean, String, boolean, boolean, Map, DeliverCallback, CancelCallback) + * @see Channel#basicConsume(String, boolean, String, boolean, boolean, Map, DeliverCallback, ConsumerShutdownSignalCallback) + * @see Channel#basicConsume(String, boolean, String, boolean, boolean, Map, DeliverCallback, CancelCallback, ConsumerShutdownSignalCallback) + * @since 5.0 + */ +@FunctionalInterface +public interface ConsumerShutdownSignalCallback { + + /** + * Called when either the channel or the underlying connection has been shut down. + * @param consumerTag the consumer tag associated with the consumer + * @param sig a {@link ShutdownSignalException} indicating the reason for the shut down + */ + void handleShutdownSignal(String consumerTag, ShutdownSignalException sig); + +} diff --git a/src/com/rabbitmq/client/ContentHeader.java b/src/main/java/com/rabbitmq/client/ContentHeader.java similarity index 52% rename from src/com/rabbitmq/client/ContentHeader.java rename to src/main/java/com/rabbitmq/client/ContentHeader.java index b8d1ae4b6e..a2171b8f16 100644 --- a/src/com/rabbitmq/client/ContentHeader.java +++ b/src/main/java/com/rabbitmq/client/ContentHeader.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; diff --git a/src/com/rabbitmq/client/DefaultConsumer.java b/src/main/java/com/rabbitmq/client/DefaultConsumer.java similarity index 74% rename from src/com/rabbitmq/client/DefaultConsumer.java rename to src/main/java/com/rabbitmq/client/DefaultConsumer.java index f526d379eb..6df1f883db 100644 --- a/src/com/rabbitmq/client/DefaultConsumer.java +++ b/src/main/java/com/rabbitmq/client/DefaultConsumer.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; @@ -41,6 +40,7 @@ public DefaultConsumer(Channel channel) { * Stores the most recently passed-in consumerTag - semantically, there should be only one. * @see Consumer#handleConsumeOk */ + @Override public void handleConsumeOk(String consumerTag) { this._consumerTag = consumerTag; } @@ -49,6 +49,7 @@ public void handleConsumeOk(String consumerTag) { * No-op implementation of {@link Consumer#handleCancelOk}. * @param consumerTag the defined consumer tag (client- or server-generated) */ + @Override public void handleCancelOk(String consumerTag) { // no work to do } @@ -57,6 +58,7 @@ public void handleCancelOk(String consumerTag) { * No-op implementation of {@link Consumer#handleCancel(String)} * @param consumerTag the defined consumer tag (client- or server-generated) */ + @Override public void handleCancel(String consumerTag) throws IOException { // no work to do } @@ -64,6 +66,7 @@ public void handleCancel(String consumerTag) throws IOException { /** * No-op implementation of {@link Consumer#handleShutdownSignal}. */ + @Override public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { // no work to do } @@ -71,6 +74,7 @@ public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig /** * No-op implementation of {@link Consumer#handleRecoverOk}. */ + @Override public void handleRecoverOk(String consumerTag) { // no work to do } @@ -78,6 +82,7 @@ public void handleRecoverOk(String consumerTag) { /** * No-op implementation of {@link Consumer#handleDelivery}. */ + @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, diff --git a/src/com/rabbitmq/client/DefaultSaslConfig.java b/src/main/java/com/rabbitmq/client/DefaultSaslConfig.java similarity index 54% rename from src/com/rabbitmq/client/DefaultSaslConfig.java rename to src/main/java/com/rabbitmq/client/DefaultSaslConfig.java index 9888f69432..938c8b9827 100644 --- a/src/com/rabbitmq/client/DefaultSaslConfig.java +++ b/src/main/java/com/rabbitmq/client/DefaultSaslConfig.java @@ -1,21 +1,21 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; +import com.rabbitmq.client.impl.AnonymousMechanism; import com.rabbitmq.client.impl.ExternalMechanism; import com.rabbitmq.client.impl.PlainMechanism; @@ -31,6 +31,7 @@ public class DefaultSaslConfig implements SaslConfig { public static final DefaultSaslConfig PLAIN = new DefaultSaslConfig("PLAIN"); public static final DefaultSaslConfig EXTERNAL = new DefaultSaslConfig("EXTERNAL"); + public static final DefaultSaslConfig ANONYMOUS = new DefaultSaslConfig("ANONYMOUS"); /** * Create a DefaultSaslConfig with an explicit mechanism to use. @@ -41,6 +42,7 @@ private DefaultSaslConfig(String mechanism) { this.mechanism = mechanism; } + @Override public SaslMechanism getSaslMechanism(String[] serverMechanisms) { Set server = new HashSet(Arrays.asList(serverMechanisms)); @@ -50,6 +52,8 @@ public SaslMechanism getSaslMechanism(String[] serverMechanisms) { } else if (mechanism.equals("EXTERNAL")) { return new ExternalMechanism(); + } else if (mechanism.equals("ANONYMOUS")) { + return new AnonymousMechanism(); } } return null; diff --git a/src/main/java/com/rabbitmq/client/DefaultSocketChannelConfigurator.java b/src/main/java/com/rabbitmq/client/DefaultSocketChannelConfigurator.java new file mode 100644 index 0000000000..3dbd82d9ff --- /dev/null +++ b/src/main/java/com/rabbitmq/client/DefaultSocketChannelConfigurator.java @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.IOException; +import java.nio.channels.SocketChannel; + +public class DefaultSocketChannelConfigurator implements SocketChannelConfigurator { + + /** + * Provides a hook to insert custom configuration of the {@link SocketChannel}s + * used to connect to an AMQP server before they connect. + * + * The default behaviour of this method is to disable Nagle's + * algorithm to get more consistently low latency. However it + * may be overridden freely and there is no requirement to retain + * this behaviour. + * + * @param socketChannel The socket channel that is to be used for the Connection + */ + @Override + public void configure(SocketChannel socketChannel) throws IOException { + socketChannel.socket().setTcpNoDelay(true); + } +} diff --git a/src/main/java/com/rabbitmq/client/DefaultSocketConfigurator.java b/src/main/java/com/rabbitmq/client/DefaultSocketConfigurator.java new file mode 100644 index 0000000000..a888386249 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/DefaultSocketConfigurator.java @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.IOException; +import java.net.Socket; + +public class DefaultSocketConfigurator implements SocketConfigurator { + /** + * Provides a hook to insert custom configuration of the sockets + * used to connect to an AMQP server before they connect. + * + * The default behaviour of this method is to disable Nagle's + * algorithm to get more consistently low latency. However it + * may be overridden freely and there is no requirement to retain + * this behaviour. + * + * @param socket The socket that is to be used for the Connection + */ + @Override + public void configure(Socket socket) throws IOException { + // disable Nagle's algorithm, for more consistently low latency + socket.setTcpNoDelay(true); + } +} diff --git a/src/main/java/com/rabbitmq/client/DeliverCallback.java b/src/main/java/com/rabbitmq/client/DeliverCallback.java new file mode 100644 index 0000000000..1d0ab0a3a3 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/DeliverCallback.java @@ -0,0 +1,43 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.IOException; +import java.util.Map; + +/** + * Callback interface to be notified when a message is delivered. + * Prefer it over {@link Consumer} for a lambda-oriented syntax, + * if you don't need to implement all the application callbacks. + * @see CancelCallback + * @see ConsumerShutdownSignalCallback + * @see Channel#basicConsume(String, boolean, String, boolean, boolean, Map, DeliverCallback, CancelCallback) + * @see Channel#basicConsume(String, boolean, String, boolean, boolean, Map, DeliverCallback, ConsumerShutdownSignalCallback) + * @see Channel#basicConsume(String, boolean, String, boolean, boolean, Map, DeliverCallback, CancelCallback, ConsumerShutdownSignalCallback) + * @since 5.0 + */ +@FunctionalInterface +public interface DeliverCallback { + + /** + * Called when a basic.deliver is received for this consumer. + * @param consumerTag the consumer tag associated with the consumer + * @param message the delivered message + * @throws IOException if the consumer encounters an I/O error while processing the message + */ + void handle(String consumerTag, Delivery message) throws IOException; + +} diff --git a/src/main/java/com/rabbitmq/client/Delivery.java b/src/main/java/com/rabbitmq/client/Delivery.java new file mode 100644 index 0000000000..ecc53525c6 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/Delivery.java @@ -0,0 +1,55 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * Encapsulates an arbitrary message - simple "bean" holder structure. + */ +public class Delivery { + private final Envelope _envelope; + private final AMQP.BasicProperties _properties; + private final byte[] _body; + + public Delivery(Envelope envelope, AMQP.BasicProperties properties, byte[] body) { + _envelope = envelope; + _properties = properties; + _body = body; + } + + /** + * Retrieve the message envelope. + * @return the message envelope + */ + public Envelope getEnvelope() { + return _envelope; + } + + /** + * Retrieve the message properties. + * @return the message properties + */ + public AMQP.BasicProperties getProperties() { + return _properties; + } + + /** + * Retrieve the message body. + * @return the message body + */ + public byte[] getBody() { + return _body; + } +} diff --git a/src/main/java/com/rabbitmq/client/DnsRecordIpAddressResolver.java b/src/main/java/com/rabbitmq/client/DnsRecordIpAddressResolver.java new file mode 100644 index 0000000000..97573295d4 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/DnsRecordIpAddressResolver.java @@ -0,0 +1,86 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +/** + * {@link AddressResolver} that resolves DNS record IPs. + * Uses {@link InetAddress} internally. + * The first returned address is used when automatic recovery is NOT enabled + * at the {@link ConnectionFactory} level. + * When automatic recovery is enabled, a random address will be picked up + * from the returned list of {@link Address}es. + */ +public class DnsRecordIpAddressResolver implements AddressResolver { + + private final Address address; + + private final boolean ssl; + + public DnsRecordIpAddressResolver(String hostname, int port, boolean ssl) { + this(new Address(hostname, port), ssl); + } + + public DnsRecordIpAddressResolver(String hostname, int port) { + this(new Address(hostname, port), false); + } + + public DnsRecordIpAddressResolver() { + this("localhost"); + } + + public DnsRecordIpAddressResolver(String hostname) { + this(new Address(hostname), false); + } + + public DnsRecordIpAddressResolver(Address address) { + this(address, false); + } + + public DnsRecordIpAddressResolver(Address address, boolean ssl) { + this.address = address; + this.ssl = ssl; + } + + /** + * Get the IP addresses from a DNS query + * @return candidate {@link Address}es + * @throws IOException if DNS resolution fails + */ + @Override + public List
getAddresses() throws UnknownHostException { + String hostName = address.getHost(); + int portNumber = ConnectionFactory.portOrDefault(address.getPort(), ssl); + + InetAddress[] inetAddresses = resolveIpAddresses(hostName); + + List
addresses = new ArrayList<>(); + for (InetAddress inetAddress : inetAddresses) { + addresses.add(new ResolvedInetAddress(hostName, inetAddress, portNumber)); + } + return addresses; + } + + protected InetAddress[] resolveIpAddresses(String hostName) throws UnknownHostException { + return InetAddress.getAllByName(hostName); + } + +} diff --git a/src/main/java/com/rabbitmq/client/DnsSrvRecordAddressResolver.java b/src/main/java/com/rabbitmq/client/DnsSrvRecordAddressResolver.java new file mode 100644 index 0000000000..454a43b19b --- /dev/null +++ b/src/main/java/com/rabbitmq/client/DnsSrvRecordAddressResolver.java @@ -0,0 +1,167 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; + +/** + * {@link AddressResolver} that resolves addresses against a DNS SRV request. + * SRV records contain the hostname and the port for a given service. + * They also support priorities, to give precedence to a given host + * over other hosts. + * Note the hosts returned by the SRV query must be resolvable by + * the DNS servers of the underlying platform (or the default ones + * specified for this Java process). This class does not issue a + * query for A records after the SRV query. + * This implementation returns the highest-priority records first. + * This behavior can be changed by overriding the {@code sort} method. + * + * This implementation uses internally the {@code com.sun.jndi.dns.DnsContextFactory} + * class for the DNS query. + * + * The first returned address is used when automatic recovery is NOT enabled + * at the {@link ConnectionFactory} level. + * When automatic recovery is enabled, a random address will be picked up + * from the returned list of {@link Address}es. + * + */ +public class DnsSrvRecordAddressResolver implements AddressResolver { + + /** + * the SRV service information. + * e.g. _sip._tcp.example.com or rabbitmq.service.consul + */ + private final String service; + + /** + * URLs of the DNS servers. + * e.g. dns://server1.example.com/example.com + * Default to {@code dns:}, that is the DNS server of the + * underlying platform. + */ + private final String dnsUrls; + + public DnsSrvRecordAddressResolver(String service) { + this(service, "dns:"); + } + + public DnsSrvRecordAddressResolver(String service, String dnsUrls) { + this.service = service; + this.dnsUrls = dnsUrls; + } + + @Override + public List
getAddresses() throws IOException { + List records = lookupSrvRecords(service, dnsUrls); + records = sort(records); + + List
addresses = new ArrayList
(); + for (SrvRecord record : records) { + addresses.add(new Address(record.getHost(), record.getPort())); + } + + return addresses; + } + + protected List lookupSrvRecords(String service, String dnsUrls) throws IOException { + Hashtable env = new Hashtable(); + env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); + env.put("java.naming.provider.url", dnsUrls); + + List records = new ArrayList(); + try { + DirContext ctx = new InitialDirContext(env); + Attributes attributes = ctx.getAttributes(service, new String[] { "SRV" }); + NamingEnumeration servers = attributes.get("srv").getAll(); + while (servers.hasMore()) { + records.add(mapSrvRecord((String) servers.next())); + } + } catch(NamingException e) { + throw new IOException("Error during DNS SRV query", e); + } + + return records; + } + + protected SrvRecord mapSrvRecord(String srvResult) { + return SrvRecord.fromSrvQueryResult(srvResult); + } + + protected List sort(List records) { + Collections.sort(records); + return records; + } + + public static class SrvRecord implements Comparable { + + private final int priority; + private final int weight; + private final int port; + private final String host; + + public SrvRecord(int priority, int weight, int port, String host) { + this.priority = priority; + this.weight = weight; + this.port = port; + int lastDotIndex = host.lastIndexOf("."); + if(lastDotIndex > 0) { + this.host = host.substring(0, lastDotIndex); + } else { + this.host = host; + } + } + + public int getPriority() { + return priority; + } + + public int getWeight() { + return weight; + } + + public int getPort() { + return port; + } + + public String getHost() { + return host; + } + + public static SrvRecord fromSrvQueryResult(String srvResult) { + String[] fields = srvResult.split(" "); + return new SrvRecord( + Integer.parseInt(fields[0]), + Integer.parseInt(fields[1]), + Integer.parseInt(fields[2]), + fields[3] + ); + } + + @Override + public int compareTo(SrvRecord o) { + return (this.priority < o.getPriority()) ? -1 : ((this.priority == o.getPriority()) ? 0 : 1); + } + } +} diff --git a/src/com/rabbitmq/client/Envelope.java b/src/main/java/com/rabbitmq/client/Envelope.java similarity index 74% rename from src/com/rabbitmq/client/Envelope.java rename to src/main/java/com/rabbitmq/client/Envelope.java index eb5d797556..68c9acd4de 100644 --- a/src/com/rabbitmq/client/Envelope.java +++ b/src/main/java/com/rabbitmq/client/Envelope.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; diff --git a/src/com/rabbitmq/client/ExceptionHandler.java b/src/main/java/com/rabbitmq/client/ExceptionHandler.java similarity index 77% rename from src/com/rabbitmq/client/ExceptionHandler.java rename to src/main/java/com/rabbitmq/client/ExceptionHandler.java index f304a1ae84..8499fc94ca 100644 --- a/src/com/rabbitmq/client/ExceptionHandler.java +++ b/src/main/java/com/rabbitmq/client/ExceptionHandler.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; @@ -39,16 +38,6 @@ public interface ExceptionHandler { */ void handleReturnListenerException(Channel channel, Throwable exception); - /** - * Perform any required exception processing for the situation - * when the driver thread for the connection has called a - * FlowListener's handleFlow method, and that method has - * thrown an exception. - * @param channel the ChannelN that held the FlowListener - * @param exception the exception thrown by FlowListener.handleFlow - */ - void handleFlowListenerException(Channel channel, Throwable exception); - /** * Perform any required exception processing for the situation * when the driver thread for the connection has called a @@ -109,7 +98,7 @@ void handleConsumerException(Channel channel, * during topology (exchanges, queues, bindings, consumers) recovery * that it can't otherwise deal with. * @param conn the Connection that caught the exception - * @param ch the Channel that caught the exception + * @param ch the Channel that caught the exception. May be null. * @param exception the exception caught in the driver thread */ diff --git a/src/com/rabbitmq/client/GetResponse.java b/src/main/java/com/rabbitmq/client/GetResponse.java similarity index 78% rename from src/com/rabbitmq/client/GetResponse.java rename to src/main/java/com/rabbitmq/client/GetResponse.java index 3c77cec022..27a53bfcb0 100644 --- a/src/com/rabbitmq/client/GetResponse.java +++ b/src/main/java/com/rabbitmq/client/GetResponse.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; diff --git a/src/com/rabbitmq/client/JDKSaslConfig.java b/src/main/java/com/rabbitmq/client/JDKSaslConfig.java similarity index 83% rename from src/com/rabbitmq/client/JDKSaslConfig.java rename to src/main/java/com/rabbitmq/client/JDKSaslConfig.java index 8e57795044..e36377db53 100644 --- a/src/com/rabbitmq/client/JDKSaslConfig.java +++ b/src/main/java/com/rabbitmq/client/JDKSaslConfig.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; @@ -65,6 +64,7 @@ public JDKSaslConfig(ConnectionFactory factory, String[] mechanisms) { this.mechanisms = Arrays.asList(mechanisms); } + @Override public SaslMechanism getSaslMechanism(String[] serverMechanisms) { Set server = new HashSet(Arrays.asList(serverMechanisms)); @@ -89,10 +89,12 @@ public JDKSaslMechanism(SaslClient client) { this.client = client; } + @Override public String getName() { return client.getMechanismName(); } + @Override public LongString handleChallenge(LongString challenge, String username, String password) { try { return LongStringHelper.asLongString(client.evaluateChallenge(challenge.getBytes())); @@ -108,6 +110,7 @@ public UsernamePasswordCallbackHandler(ConnectionFactory factory) { this.factory = factory; } + @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback: callbacks) { if (callback instanceof NameCallback) { diff --git a/src/main/java/com/rabbitmq/client/ListAddressResolver.java b/src/main/java/com/rabbitmq/client/ListAddressResolver.java new file mode 100644 index 0000000000..fb6bba75f9 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ListAddressResolver.java @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.util.List; + +/** + * Simple implementation of {@link AddressResolver} that returns a fixed list. + */ +public class ListAddressResolver implements AddressResolver { + + private final List
addresses; + + public ListAddressResolver(List
addresses) { + this.addresses = addresses; + } + + @Override + public List
getAddresses() { + return addresses; + } +} diff --git a/src/main/java/com/rabbitmq/client/LongString.java b/src/main/java/com/rabbitmq/client/LongString.java new file mode 100644 index 0000000000..8e78ef562b --- /dev/null +++ b/src/main/java/com/rabbitmq/client/LongString.java @@ -0,0 +1,62 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import java.io.DataInputStream; +import java.io.IOException; + +/** + * An object providing access to a LongString. + * This might be implemented to read directly from connection + * socket, depending on the size of the content to be read - + * long strings may contain up to 4Gb of content. + */ +public interface LongString +{ + public static final long MAX_LENGTH = 0xffffffffL; + + /** + * @return the length of the string in bytes between 0 and MAX_LENGTH (inclusive) + */ + public long length(); + + /** + * Get the content stream. + * Repeated calls to this function return the same stream, + * which may not support rewind. + * @return An input stream that reads the content of the string + * @throws IOException if an error is encountered + */ + public DataInputStream getStream() throws IOException; + + /** + * Get the content as a byte array. This need not be a copy. Updates to the + * returned array may change the value of the string. + * Repeated calls to this function may return the same array. + * This function will fail if this string's length is greater than {@link Integer#MAX_VALUE}, + * throwing an {@link IllegalStateException}. + * @return the array of bytes containing the content of the {@link LongString} + */ + public byte [] getBytes(); + + /** + * Get the content as a String. Uses UTF-8 as encoding. + * @return he content of the {@link LongString} as a string + */ + @Override + public String toString(); +} diff --git a/src/main/java/com/rabbitmq/client/MalformedFrameException.java b/src/main/java/com/rabbitmq/client/MalformedFrameException.java new file mode 100644 index 0000000000..61b9871208 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/MalformedFrameException.java @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import java.io.IOException; + +/** + * Encapsulates a frame format error at the wire level. + */ +public class MalformedFrameException extends IOException { + /** Standard serialization ID. */ + private static final long serialVersionUID = 1L; + + /** + * Instantiate a MalformedFrameException. + * @param reason a string describing the exception + */ + public MalformedFrameException(String reason) { + super(reason); + } +} diff --git a/src/com/rabbitmq/client/MapRpcServer.java b/src/main/java/com/rabbitmq/client/MapRpcServer.java similarity index 72% rename from src/com/rabbitmq/client/MapRpcServer.java rename to src/main/java/com/rabbitmq/client/MapRpcServer.java index 426332b690..02a271d85a 100644 --- a/src/com/rabbitmq/client/MapRpcServer.java +++ b/src/main/java/com/rabbitmq/client/MapRpcServer.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; @@ -44,6 +43,7 @@ public MapRpcServer(Channel channel, String queueName) throws IOException /** * Overridden to delegate to handleMapCall. */ + @Override public byte[] handleCall(byte[] requestBody, AMQP.BasicProperties replyProperties) { try { @@ -75,7 +75,7 @@ public static byte[] encode(Map reply) } /** - * Delegates to handleMapCall(Map). + * Delegates to {@link MapRpcServer#handleMapCall(Map)}. */ public Map handleMapCall(Map request, AMQP.BasicProperties replyProperties) @@ -84,7 +84,7 @@ public Map handleMapCall(Map request, } /** - * Default implementation - override in subclasses. Returns the empty string. + * Default implementation override in subclasses. Returns the empty string. */ public Map handleMapCall(Map request) { @@ -94,6 +94,7 @@ public Map handleMapCall(Map request) /** * Overridden to delegate to handleMapCast. */ + @Override public void handleCast(byte[] requestBody) { try { @@ -104,7 +105,7 @@ public void handleCast(byte[] requestBody) } /** - * Default implementation - override in subclasses. Does nothing. + * Default implementation override in subclasses. Does nothing. */ public void handleMapCast(Map requestBody) { // Do nothing. diff --git a/src/com/rabbitmq/client/MessageProperties.java b/src/main/java/com/rabbitmq/client/MessageProperties.java similarity index 78% rename from src/com/rabbitmq/client/MessageProperties.java rename to src/main/java/com/rabbitmq/client/MessageProperties.java index 0b30657101..910f72ebba 100644 --- a/src/com/rabbitmq/client/MessageProperties.java +++ b/src/main/java/com/rabbitmq/client/MessageProperties.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; diff --git a/src/main/java/com/rabbitmq/client/Method.java b/src/main/java/com/rabbitmq/client/Method.java new file mode 100644 index 0000000000..93835f16b6 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/Method.java @@ -0,0 +1,42 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +/** + * Public interface to objects representing an AMQP 0-9-1 method + * @see https://www.rabbitmq.com/specification.html. + */ + +public interface Method { + /** + * Retrieve the protocol class ID + * @return the AMQP protocol class ID of this Method + */ + int protocolClassId(); /* properly an unsigned short */ + + /** + * Retrieve the protocol method ID + * @return the AMQP protocol method ID of this Method + */ + int protocolMethodId(); /* properly an unsigned short */ + + /** + * Retrieve the method name + * @return the AMQP protocol method name of this Method + */ + String protocolMethodName(); +} diff --git a/src/main/java/com/rabbitmq/client/MetricsCollector.java b/src/main/java/com/rabbitmq/client/MetricsCollector.java new file mode 100644 index 0000000000..e09d69d3c9 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/MetricsCollector.java @@ -0,0 +1,77 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * Interface to gather execution data of the client. + * Note transactions are not supported: they deal with + * publishing and acknowledgments and the collector contract + * assumes then that published messages and acks sent + * in a transaction are always counted, even if the + * transaction is rolled back. + * + */ +public interface MetricsCollector { + + void newConnection(Connection connection); + + void closeConnection(Connection connection); + + void newChannel(Channel channel); + + void closeChannel(Channel channel); + + void basicPublish(Channel channel, long deliveryTag); + + default void basicPublishFailure(Channel channel, Throwable cause) { + + } + + default void basicPublishAck(Channel channel, long deliveryTag, boolean multiple) { + + } + + default void basicPublishNack(Channel channel, long deliveryTag, boolean multiple) { + + } + + default void basicPublishUnrouted(Channel channel) { + + } + + void consumedMessage(Channel channel, long deliveryTag, boolean autoAck); + + void consumedMessage(Channel channel, long deliveryTag, String consumerTag); + + void basicAck(Channel channel, long deliveryTag, boolean multiple); + + void basicNack(Channel channel, long deliveryTag); + + default void basicNack(Channel channel, long deliveryTag, boolean requeue) { + this.basicNack(channel, deliveryTag); + } + + void basicReject(Channel channel, long deliveryTag); + + default void basicReject(Channel channel, long deliveryTag, boolean requeue) { + this.basicReject(channel, deliveryTag); + } + + void basicConsume(Channel channel, String consumerTag, boolean autoAck); + + void basicCancel(Channel channel, String consumerTag); + +} \ No newline at end of file diff --git a/src/main/java/com/rabbitmq/client/MissedHeartbeatException.java b/src/main/java/com/rabbitmq/client/MissedHeartbeatException.java new file mode 100644 index 0000000000..664515abb4 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/MissedHeartbeatException.java @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import java.net.SocketTimeoutException; + +/** + * Encapsulates an exception indicating that the connection has missed too many heartbeats + * and is being shut down. + */ + +public class MissedHeartbeatException extends SocketTimeoutException { + private static final long serialVersionUID = 1L; + + public MissedHeartbeatException(String reason) { + super(reason); + } +} diff --git a/src/main/java/com/rabbitmq/client/NoOpMetricsCollector.java b/src/main/java/com/rabbitmq/client/NoOpMetricsCollector.java new file mode 100644 index 0000000000..d50c3df618 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/NoOpMetricsCollector.java @@ -0,0 +1,113 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * + */ +public class NoOpMetricsCollector implements MetricsCollector { + + @Override + public void newConnection(Connection connection) { + + } + + @Override + public void closeConnection(Connection connection) { + + } + + @Override + public void newChannel(Channel channel) { + + } + + @Override + public void closeChannel(Channel channel) { + + } + + @Override + public void basicAck(Channel channel, long deliveryTag, boolean multiple) { + + } + + @Override + public void basicNack(Channel channel, long deliveryTag) { + + } + + @Override + public void basicNack(Channel channel, long deliveryTag, boolean requeue) { + + } + + @Override + public void basicReject(Channel channel, long deliveryTag) { + + } + + @Override + public void basicReject(Channel channel, long deliveryTag, boolean requeue) { + + } + + @Override + public void basicConsume(Channel channel, String consumerTag, boolean autoAck) { + + } + + @Override + public void basicCancel(Channel channel, String consumerTag) { + + } + + @Override + public void basicPublish(Channel channel, long deliveryTag) { + + } + + @Override + public void basicPublishFailure(Channel channel, Throwable cause) { + + } + + @Override + public void basicPublishAck(Channel channel, long deliveryTag, boolean multiple) { + + } + + @Override + public void basicPublishNack(Channel channel, long deliveryTag, boolean multiple) { + + } + + @Override + public void basicPublishUnrouted(Channel channel) { + + } + + @Override + public void consumedMessage(Channel channel, long deliveryTag, boolean autoAck) { + + } + + @Override + public void consumedMessage(Channel channel, long deliveryTag, String consumerTag) { + + } + +} diff --git a/src/main/java/com/rabbitmq/client/PossibleAuthenticationFailureException.java b/src/main/java/com/rabbitmq/client/PossibleAuthenticationFailureException.java new file mode 100644 index 0000000000..679b1a37d7 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/PossibleAuthenticationFailureException.java @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.IOException; + +/** + * Thrown when the likely cause is an authentication failure. + */ +public class PossibleAuthenticationFailureException extends IOException +{ + /** Default for non-checking. */ + private static final long serialVersionUID = 1L; + + public PossibleAuthenticationFailureException(Throwable cause) + { + super("Possibly caused by authentication failure"); + super.initCause(cause); + } + + public PossibleAuthenticationFailureException(String reason) + { + super(reason); + } +} diff --git a/src/com/rabbitmq/client/ProtocolVersionMismatchException.java b/src/main/java/com/rabbitmq/client/ProtocolVersionMismatchException.java similarity index 69% rename from src/com/rabbitmq/client/ProtocolVersionMismatchException.java rename to src/main/java/com/rabbitmq/client/ProtocolVersionMismatchException.java index f247ad68d2..e61e9e5b64 100644 --- a/src/com/rabbitmq/client/ProtocolVersionMismatchException.java +++ b/src/main/java/com/rabbitmq/client/ProtocolVersionMismatchException.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; diff --git a/src/main/java/com/rabbitmq/client/Recoverable.java b/src/main/java/com/rabbitmq/client/Recoverable.java new file mode 100644 index 0000000000..3288d3f685 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/Recoverable.java @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * Provides a way to register (network, AMQP 0-9-1) connection recovery + * callbacks. + * + * When connection recovery is enabled via {@link ConnectionFactory}, + * {@link ConnectionFactory#newConnection()} and {@link Connection#createChannel()} + * return {@link Recoverable} connections and channels. + * + * @see com.rabbitmq.client.impl.recovery.AutorecoveringConnection + * @see com.rabbitmq.client.impl.recovery.AutorecoveringChannel + */ +public interface Recoverable { + /** + * Registers a connection recovery callback. + * + * @param listener Callback function + */ + void addRecoveryListener(RecoveryListener listener); + + void removeRecoveryListener(RecoveryListener listener); +} diff --git a/src/main/java/com/rabbitmq/client/RecoverableChannel.java b/src/main/java/com/rabbitmq/client/RecoverableChannel.java new file mode 100644 index 0000000000..87d1e55f68 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/RecoverableChannel.java @@ -0,0 +1,13 @@ +package com.rabbitmq.client; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Recoverable; + +/** + * Convenient interface when working against auto-recovery channels. + * + * @since 4.0.0 + */ +public interface RecoverableChannel extends Recoverable, Channel { + +} diff --git a/src/main/java/com/rabbitmq/client/RecoverableConnection.java b/src/main/java/com/rabbitmq/client/RecoverableConnection.java new file mode 100644 index 0000000000..81271c7c7e --- /dev/null +++ b/src/main/java/com/rabbitmq/client/RecoverableConnection.java @@ -0,0 +1,10 @@ +package com.rabbitmq.client; + +/** + * Convenient interface when working against auto-recovery connections. + * + * @since 4.0.0 + */ +public interface RecoverableConnection extends Recoverable, Connection { + +} diff --git a/src/main/java/com/rabbitmq/client/RecoveryDelayHandler.java b/src/main/java/com/rabbitmq/client/RecoveryDelayHandler.java new file mode 100644 index 0000000000..99dc500714 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/RecoveryDelayHandler.java @@ -0,0 +1,95 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A RecoveryDelayHandler is used to tell automatic recovery how long to sleep between reconnect attempts. + * + * @since 4.3.0 + */ +public interface RecoveryDelayHandler { + + /** + * Get the time to sleep (in milliseconds) before attempting to reconnect and recover again. + * This method will be called with recoveryAttempts=0 before the first recovery attempt and then again after each failed recovery. + * + * @param recoveryAttempts + * The number of recovery attempts so far. + * @return the delay in milliseconds + */ + long getDelay(final int recoveryAttempts); + + /** + * Basic implementation of {@link RecoveryDelayHandler} that returns the {@link ConnectionFactory#getNetworkRecoveryInterval() network recovery interval} each time. + */ + class DefaultRecoveryDelayHandler implements RecoveryDelayHandler { + + private final long networkRecoveryInterval; + + /** + * Default Constructor + * @param networkRecoveryInterval + * recovery delay time in millis + */ + public DefaultRecoveryDelayHandler(final long networkRecoveryInterval) { + this.networkRecoveryInterval = networkRecoveryInterval; + } + + @Override + public long getDelay(int recoveryAttempts) { + return networkRecoveryInterval; + } + } + + /** + * Backoff implementation of {@link RecoveryDelayHandler} that uses the Fibonacci sequence (by default) to increase the recovery delay time after each failed attempt. + * You can optionally use your own backoff sequence. + */ + class ExponentialBackoffDelayHandler implements RecoveryDelayHandler { + + private final List sequence; + + /** + * Default Constructor. Uses the following sequence: 2000, 3000, 5000, 8000, 13000, 21000, 34000 + */ + public ExponentialBackoffDelayHandler() { + sequence = Arrays.asList(2000L, 3000L, 5000L, 8000L, 13000L, 21000L, 34000L); + } + + /** + * Constructor for passing your own backoff sequence + * + * @param sequence + * List of recovery delay values in milliseconds. + * @throws IllegalArgumentException if the sequence is null or empty + */ + public ExponentialBackoffDelayHandler(final List sequence) { + if (sequence == null || sequence.isEmpty()) + throw new IllegalArgumentException(); + this.sequence = Collections.unmodifiableList(sequence); + } + + @Override + public long getDelay(int recoveryAttempts) { + int index = recoveryAttempts >= sequence.size() ? sequence.size() - 1 : recoveryAttempts; + return sequence.get(index); + } + } +} diff --git a/src/main/java/com/rabbitmq/client/RecoveryListener.java b/src/main/java/com/rabbitmq/client/RecoveryListener.java new file mode 100644 index 0000000000..e4bd9e6e71 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/RecoveryListener.java @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * A RecoveryListener receives notifications about completed automatic connection + * recovery. + * + * @since 3.3.0 + */ +public interface RecoveryListener { + /** + * Invoked when automatic connection recovery has completed. + * This includes topology recovery if it was enabled. + * @param recoverable a {@link Recoverable} connection. + */ + void handleRecovery(Recoverable recoverable); + + /** + * Invoked before automatic connection recovery starts. + * This means no recovery steps were performed at this point + * during recovery process. + * @param recoverable a {@link Recoverable} connection. + */ + void handleRecoveryStarted(Recoverable recoverable); + + /** + * Invoked before automatic topology recovery starts. + * This means that the connection and channel recovery has completed + * and that exchange/queue/binding/consumer recovery is about to begin. + * @param recoverable a {@link Recoverable} connection. + */ + default void handleTopologyRecoveryStarted(Recoverable recoverable) {} +} diff --git a/src/main/java/com/rabbitmq/client/ResolvedInetAddress.java b/src/main/java/com/rabbitmq/client/ResolvedInetAddress.java new file mode 100644 index 0000000000..ca72abda81 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ResolvedInetAddress.java @@ -0,0 +1,18 @@ +package com.rabbitmq.client; + +import java.net.InetAddress; +import java.net.InetSocketAddress; + +public class ResolvedInetAddress extends Address { + private final InetAddress inetAddress; + + public ResolvedInetAddress(String originalHostname, InetAddress inetAddress, int port) { + super(originalHostname, port); + this.inetAddress = inetAddress; + } + + @Override + public InetSocketAddress toInetSocketAddress(int port) { + return new InetSocketAddress(inetAddress, port); + } +} diff --git a/src/main/java/com/rabbitmq/client/Return.java b/src/main/java/com/rabbitmq/client/Return.java new file mode 100644 index 0000000000..7622c6269d --- /dev/null +++ b/src/main/java/com/rabbitmq/client/Return.java @@ -0,0 +1,62 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * + */ +public class Return { + + private final int replyCode; + private final String replyText; + private final String exchange; + private final String routingKey; + private final AMQP.BasicProperties properties; + private final byte[] body; + + public Return(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) { + this.replyCode = replyCode; + this.replyText = replyText; + this.exchange = exchange; + this.routingKey = routingKey; + this.properties = properties; + this.body = body; + } + + public int getReplyCode() { + return replyCode; + } + + public String getReplyText() { + return replyText; + } + + public String getExchange() { + return exchange; + } + + public String getRoutingKey() { + return routingKey; + } + + public AMQP.BasicProperties getProperties() { + return properties; + } + + public byte[] getBody() { + return body; + } +} diff --git a/src/main/java/com/rabbitmq/client/ReturnCallback.java b/src/main/java/com/rabbitmq/client/ReturnCallback.java new file mode 100644 index 0000000000..89f8e4cbb1 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ReturnCallback.java @@ -0,0 +1,33 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * Implement this interface in order to be notified of failed + * deliveries when basicPublish is called with "mandatory" or + * "immediate" flags set. + * Prefer this interface over {@link ReturnListener} for + * a simpler, lambda-oriented syntax. + * @see Channel#basicPublish + * @see ReturnListener + * @see Return + */ +@FunctionalInterface +public interface ReturnCallback { + + void handle(Return returnMessage); + +} diff --git a/src/main/java/com/rabbitmq/client/ReturnListener.java b/src/main/java/com/rabbitmq/client/ReturnListener.java new file mode 100644 index 0000000000..e4af62c82d --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ReturnListener.java @@ -0,0 +1,36 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import java.io.IOException; + +/** + * Implement this interface in order to be notified of failed + * deliveries when basicPublish is called with "mandatory" or + * "immediate" flags set. + * For a lambda-oriented syntax, use {@link ReturnCallback}. + * @see Channel#basicPublish + */ +public interface ReturnListener { + void handleReturn(int replyCode, + String replyText, + String exchange, + String routingKey, + AMQP.BasicProperties properties, + byte[] body) + throws IOException; +} diff --git a/src/main/java/com/rabbitmq/client/RpcClient.java b/src/main/java/com/rabbitmq/client/RpcClient.java new file mode 100644 index 0000000000..a44a6e52ec --- /dev/null +++ b/src/main/java/com/rabbitmq/client/RpcClient.java @@ -0,0 +1,513 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; +import java.util.function.Supplier; + +import com.rabbitmq.client.impl.MethodArgumentReader; +import com.rabbitmq.client.impl.MethodArgumentWriter; +import com.rabbitmq.client.impl.ValueReader; +import com.rabbitmq.client.impl.ValueWriter; +import com.rabbitmq.utility.BlockingCell; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Convenience class which manages simple RPC-style communication. + * The class is agnostic about the format of RPC arguments / return values. + * It simply provides a mechanism for sending a message to an exchange with a given routing key, + * and waiting for a response. +*/ +public class RpcClient implements AutoCloseable { + + private static final Logger LOGGER = LoggerFactory.getLogger(RpcClient.class); + + /** Channel we are communicating on */ + private final Channel _channel; + /** Exchange to send requests to */ + private final String _exchange; + /** Routing key to use for requests */ + private final String _routingKey; + /** Queue where the server should put the reply */ + private final String _replyTo; + /** timeout to use on call responses */ + private final int _timeout; + /** NO_TIMEOUT value must match convention on {@link BlockingCell#uninterruptibleGet(int)} */ + protected final static int NO_TIMEOUT = -1; + /** Whether to publish RPC requests with the mandatory flag or not. */ + private final boolean _useMandatory; + /** closed flag */ + private final AtomicBoolean closed = new AtomicBoolean(false); + + public final static Function DEFAULT_REPLY_HANDLER = reply -> { + if (reply instanceof ShutdownSignalException) { + ShutdownSignalException sig = (ShutdownSignalException) reply; + ShutdownSignalException wrapper = + new ShutdownSignalException(sig.isHardError(), + sig.isInitiatedByApplication(), + sig.getReason(), + sig.getReference()); + wrapper.initCause(sig); + throw wrapper; + } else if (reply instanceof UnroutableRpcRequestException) { + throw (UnroutableRpcRequestException) reply; + } else { + return (Response) reply; + } + }; + + private final Function _replyHandler; + + /** Map from request correlation ID to continuation BlockingCell */ + private final Map> _continuationMap = new HashMap>(); + + /** + * Generates correlation ID for each request. + * + * @since 5.9.0 + */ + private final Supplier _correlationIdSupplier; + private final ReturnListener _returnListener; + + private String lastCorrelationId = "0"; + + /** Consumer attached to our reply queue */ + private final DefaultConsumer _consumer; + + /** + * Construct a {@link RpcClient} with the passed-in {@link RpcClientParams}. + * + * @param params + * @throws IOException + * @see RpcClientParams + * @since 5.6.0 + */ + public RpcClient(RpcClientParams params) throws + IOException { + _channel = params.getChannel(); + _exchange = params.getExchange(); + _routingKey = params.getRoutingKey(); + _replyTo = params.getReplyTo(); + if (params.getTimeout() < NO_TIMEOUT) { + throw new IllegalArgumentException("Timeout argument must be NO_TIMEOUT(-1) or non-negative."); + } + _timeout = params.getTimeout(); + _useMandatory = params.shouldUseMandatory(); + _replyHandler = params.getReplyHandler(); + _correlationIdSupplier = params.getCorrelationIdSupplier(); + + _consumer = setupConsumer(); + if (_useMandatory) { + this._returnListener = this._channel.addReturnListener(returnMessage -> { + synchronized (_continuationMap) { + String replyId = returnMessage.getProperties().getCorrelationId(); + BlockingCell blocker = _continuationMap.remove(replyId); + if (blocker == null) { + // Entry should have been removed if request timed out, + // log a warning nevertheless. + LOGGER.warn("No outstanding request for correlation ID {}", replyId); + } else { + blocker.set(new UnroutableRpcRequestException(returnMessage)); + } + } + }); + } else { + this._returnListener = null; + } + } + + /** + * Private API - ensures the RpcClient is correctly open. + * @throws IOException if an error is encountered + */ + private void checkNotClosed() throws IOException { + if (this.closed.get()) { + throw new EOFException("RpcClient is closed"); + } + } + + /** + * Public API - cancels the consumer, thus deleting the temporary queue, and marks the RpcClient as closed. + * @throws IOException if an error is encountered + */ + @Override + public void close() throws IOException { + if (this.closed.compareAndSet(false, true)) { + _channel.basicCancel(_consumer.getConsumerTag()); + if (this._returnListener != null) { + _channel.removeReturnListener(this._returnListener); + } + } + } + + /** + * Registers a consumer on the reply queue. + * @throws IOException if an error is encountered + * @return the newly created and registered consumer + */ + protected DefaultConsumer setupConsumer() throws IOException { + DefaultConsumer consumer = new DefaultConsumer(_channel) { + @Override + public void handleShutdownSignal(String consumerTag, + ShutdownSignalException signal) { + synchronized (_continuationMap) { + for (Entry> entry : _continuationMap.entrySet()) { + entry.getValue().set(signal); + } + closed.set(true); + } + } + + @Override + public void handleDelivery(String consumerTag, + Envelope envelope, + AMQP.BasicProperties properties, + byte[] body) { + synchronized (_continuationMap) { + String replyId = properties.getCorrelationId(); + BlockingCell blocker =_continuationMap.remove(replyId); + if (blocker == null) { + // Entry should have been removed if request timed out, + // log a warning nevertheless. + LOGGER.warn("No outstanding request for correlation ID {}", replyId); + } else { + blocker.set(new Response(consumerTag, envelope, properties, body)); + } + } + } + }; + _channel.basicConsume(_replyTo, true, consumer); + return consumer; + } + + public void publish(AMQP.BasicProperties props, byte[] message) + throws IOException + { + _channel.basicPublish(_exchange, _routingKey, _useMandatory, props, message); + } + + public Response doCall(AMQP.BasicProperties props, byte[] message) + throws IOException, TimeoutException { + return doCall(props, message, _timeout); + } + + public Response doCall(AMQP.BasicProperties props, byte[] message, int timeout) + throws IOException, ShutdownSignalException, TimeoutException { + checkNotClosed(); + BlockingCell k = new BlockingCell(); + String replyId; + synchronized (_continuationMap) { + replyId = _correlationIdSupplier.get(); + lastCorrelationId = replyId; + props = ((props==null) ? new AMQP.BasicProperties.Builder() : props.builder()) + .correlationId(replyId).replyTo(_replyTo).build(); + _continuationMap.put(replyId, k); + } + publish(props, message); + Object reply; + try { + reply = k.uninterruptibleGet(timeout); + } catch (TimeoutException ex) { + // Avoid potential leak. This entry is no longer needed by caller. + _continuationMap.remove(replyId); + throw ex; + } + return _replyHandler.apply(reply); + } + + public byte[] primitiveCall(AMQP.BasicProperties props, byte[] message) + throws IOException, ShutdownSignalException, TimeoutException + { + return primitiveCall(props, message, _timeout); + } + + public byte[] primitiveCall(AMQP.BasicProperties props, byte[] message, int timeout) + throws IOException, ShutdownSignalException, TimeoutException + { + return doCall(props, message, timeout).getBody(); + } + + /** + * Perform a simple byte-array-based RPC roundtrip. + * @param message the byte array request message to send + * @return the byte array response received + * @throws ShutdownSignalException if the connection dies during our wait + * @throws IOException if an error is encountered + * @throws TimeoutException if a response is not received within the configured timeout + */ + public byte[] primitiveCall(byte[] message) + throws IOException, ShutdownSignalException, TimeoutException { + return primitiveCall(null, message); + } + + /** + * Perform a simple byte-array-based RPC roundtrip + * + * Useful if you need to get at more than just the body of the message + * + * @param message the byte array request message to send + * @return The response object is an envelope that contains all of the data provided to the `handleDelivery` consumer + * @throws ShutdownSignalException if the connection dies during our wait + * @throws IOException if an error is encountered + * @throws TimeoutException if a response is not received within the configured timeout + */ + public Response responseCall(byte[] message) throws IOException, ShutdownSignalException, TimeoutException { + return responseCall(message, _timeout); + } + + /** + * Perform a simple byte-array-based RPC roundtrip + * + * Useful if you need to get at more than just the body of the message + * + * @param message the byte array request message to send + * @param timeout milliseconds before timing out on wait for response + * @return The response object is an envelope that contains all of the data provided to the `handleDelivery` consumer + * @throws ShutdownSignalException if the connection dies during our wait + * @throws IOException if an error is encountered + * @throws TimeoutException if a response is not received within the configured timeout + */ + public Response responseCall(byte[] message, int timeout) throws IOException, ShutdownSignalException, TimeoutException { + return doCall(null, message, timeout); + } + + /** + * Perform a simple string-based RPC roundtrip. + * @param message the string request message to send + * @return the string response received + * @throws ShutdownSignalException if the connection dies during our wait + * @throws IOException if an error is encountered + * @throws TimeoutException if a timeout occurs before the response is received + */ + @SuppressWarnings("unused") + public String stringCall(String message) + throws IOException, ShutdownSignalException, TimeoutException + { + byte[] request; + try { + request = message.getBytes(StringRpcServer.STRING_ENCODING); + } catch (IOException _e) { + request = message.getBytes(); + } + byte[] reply = primitiveCall(request); + try { + return new String(reply, StringRpcServer.STRING_ENCODING); + } catch (IOException _e) { + return new String(reply); + } + } + + /** + * Perform an AMQP wire-protocol-table based RPC roundtrip

+ * + * There are some restrictions on the values appearing in the table:
+ * they must be of type {@link String}, {@link LongString}, {@link Integer}, {@link java.math.BigDecimal}, {@link Date}, + * or (recursively) a {@link Map} of the enclosing type. + * + * @param message the table to send + * @return the table received + * @throws ShutdownSignalException if the connection dies during our wait + * @throws IOException if an error is encountered + * @throws TimeoutException if a timeout occurs before a response is received + */ + public Map mapCall(Map message) + throws IOException, ShutdownSignalException, TimeoutException + { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + MethodArgumentWriter writer = new MethodArgumentWriter(new ValueWriter(new DataOutputStream(buffer))); + writer.writeTable(message); + writer.flush(); + byte[] reply = primitiveCall(buffer.toByteArray()); + MethodArgumentReader reader = + new MethodArgumentReader(new ValueReader(new DataInputStream(new ByteArrayInputStream(reply)))); + return reader.readTable(); + } + + /** + * Perform an AMQP wire-protocol-table based RPC roundtrip, first + * constructing the table from an array of alternating keys (in + * even-numbered elements, starting at zero) and values (in + * odd-numbered elements, starting at one)
+ * Restrictions on value arguments apply as in {@link RpcClient#mapCall(Map)}. + * + * @param keyValuePairs alternating {key, value, key, value, ...} data to send + * @return the table received + * @throws ShutdownSignalException if the connection dies during our wait + * @throws IOException if an error is encountered + * @throws TimeoutException if a timeout occurs before a response is received + */ + public Map mapCall(Object[] keyValuePairs) + throws IOException, ShutdownSignalException, TimeoutException + { + Map message = new HashMap(); + for (int i = 0; i < keyValuePairs.length; i += 2) { + message.put((String) keyValuePairs[i], keyValuePairs[i + 1]); + } + return mapCall(message); + } + + /** + * Retrieve the channel. + * @return the channel to which this client is connected + */ + public Channel getChannel() { + return _channel; + } + + /** + * Retrieve the exchange. + * @return the exchange to which this client is connected + */ + public String getExchange() { + return _exchange; + } + + /** + * Retrieve the routing key. + * @return the routing key for messages to this client + */ + public String getRoutingKey() { + return _routingKey; + } + + /** + * Retrieve the continuation map. + * @return the map of objects to blocking cells for this client + */ + public Map> getContinuationMap() { + return _continuationMap; + } + + /** + * Retrieve the last correlation id used. + *

+ * Note as of 5.9.0, correlation IDs may not always be integers + * (by default, they are). + * This method will try to parse the last correlation ID string + * as an integer, so this may result in {@link NumberFormatException} + * if the correlation ID supplier provided by + * {@link RpcClientParams#correlationIdSupplier(Supplier)} + * does not generate appropriate IDs. + * + * @return the most recently used correlation id + * @see RpcClientParams#correlationIdSupplier(Supplier) + */ + public int getCorrelationId() { + return Integer.valueOf(this.lastCorrelationId); + } + + /** + * Retrieve the consumer. + * @return an interface to the client's consumer object + */ + public Consumer getConsumer() { + return _consumer; + } + + /** + * The response object is an envelope that contains all of the data provided to the `handleDelivery` consumer + */ + public static class Response { + protected String consumerTag; + protected Envelope envelope; + protected AMQP.BasicProperties properties; + protected byte[] body; + + public Response() { + } + + public Response( + final String consumerTag, final Envelope envelope, final AMQP.BasicProperties properties, + final byte[] body) { + this.consumerTag = consumerTag; + this.envelope = envelope; + this.properties = properties; + this.body = body; + } + + public String getConsumerTag() { + return consumerTag; + } + + public Envelope getEnvelope() { + return envelope; + } + + public AMQP.BasicProperties getProperties() { + return properties; + } + + public byte[] getBody() { + return body; + } + } + + /** + * Creates generation IDs as a sequence of integers. + * + * @return + * @see RpcClientParams#correlationIdSupplier(Supplier) + * @since 5.9.0 + */ + public static Supplier incrementingCorrelationIdSupplier() { + return incrementingCorrelationIdSupplier(""); + } + + /** + * Creates generation IDs as a sequence of integers, with the provided prefix. + * + * @param prefix + * @return + * @see RpcClientParams#correlationIdSupplier(Supplier) + * @since 5.9.0 + */ + public static Supplier incrementingCorrelationIdSupplier(String prefix) { + return new IncrementingCorrelationIdSupplier(prefix); + } + + /** + * @since 5.9.0 + */ + private static class IncrementingCorrelationIdSupplier implements Supplier { + + private final String prefix; + private int correlationId; + + public IncrementingCorrelationIdSupplier(String prefix) { + this.prefix = prefix; + } + + @Override + public String get() { + return prefix + ++correlationId; + } + + } +} + diff --git a/src/main/java/com/rabbitmq/client/RpcClientParams.java b/src/main/java/com/rabbitmq/client/RpcClientParams.java new file mode 100644 index 0000000000..b7bcbfee0b --- /dev/null +++ b/src/main/java/com/rabbitmq/client/RpcClientParams.java @@ -0,0 +1,215 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Holder class to configure a {@link RpcClient}. + * + * @see RpcClient#RpcClient(RpcClientParams) + * @since 5.6.0 + */ +public class RpcClientParams { + + /** + * Channel we are communicating on + */ + private Channel channel; + /** + * Exchange to send requests to + */ + private String exchange; + /** + * Routing key to use for requests + */ + private String routingKey; + /** + * Queue where the server should put the reply + */ + private String replyTo = "amq.rabbitmq.reply-to"; + /** + * Timeout in milliseconds to use on call responses + */ + private int timeout = RpcClient.NO_TIMEOUT; + /** + * Whether to publish RPC requests with the mandatory flag or not. + */ + private boolean useMandatory = false; + /** + * Behavior to handle reply messages. + */ + private Function replyHandler = RpcClient.DEFAULT_REPLY_HANDLER; + + /** + * Logic to generate correlation IDs. + */ + private Supplier correlationIdSupplier = RpcClient.incrementingCorrelationIdSupplier(); + + /** + * Set the channel to use for communication. + * + * @return + */ + public Channel getChannel() { + return channel; + } + + public RpcClientParams channel(Channel channel) { + this.channel = channel; + return this; + } + + /** + * Set the exchange to send requests to. + * + * @return + */ + public String getExchange() { + return exchange; + } + + public RpcClientParams exchange(String exchange) { + this.exchange = exchange; + return this; + } + + public String getRoutingKey() { + return routingKey; + } + + /** + * Set the routing key to use for requests. + * + * @param routingKey + * @return + */ + public RpcClientParams routingKey(String routingKey) { + this.routingKey = routingKey; + return this; + } + + public String getReplyTo() { + return replyTo; + } + + /** + * Set the queue where the server should put replies on. + *

+ * The default is to use + * Direct Reply-to. + * Using another value will cause the creation of a temporary private + * auto-delete queue. + *

+ * The default shouldn't be changed for performance reasons. + * + * @param replyTo + * @return + */ + public RpcClientParams replyTo(String replyTo) { + this.replyTo = replyTo; + return this; + } + + public int getTimeout() { + return timeout; + } + + /** + * Set the timeout in milliseconds to use on call responses. + * + * @param timeout + * @return + */ + public RpcClientParams timeout(int timeout) { + this.timeout = timeout; + return this; + } + + /** + * Whether to publish RPC requests with the mandatory flag or not. + *

+ * Default is to not publish requests with the mandatory flag + * set to true. + *

+ * When set to true, unroutable requests will result + * in {@link UnroutableRpcRequestException} exceptions thrown. + * Use a custom reply handler to change this behavior. + * + * @param useMandatory + * @return + * @see #replyHandler(Function) + */ + public RpcClientParams useMandatory(boolean useMandatory) { + this.useMandatory = useMandatory; + return this; + } + + /** + * Instructs to use the mandatory flag when publishing RPC requests. + *

+ * Unroutable requests will result in {@link UnroutableRpcRequestException} exceptions + * thrown. Use a custom reply handler to change this behavior. + * + * @return + * @see #replyHandler(Function) + */ + public RpcClientParams useMandatory() { + return useMandatory(true); + } + + public boolean shouldUseMandatory() { + return useMandatory; + } + + /** + * Logic to generate correlation IDs. + * + * @param correlationIdGenerator + * @return + * @since 5.9.0 + */ + public RpcClientParams correlationIdSupplier(Supplier correlationIdGenerator) { + this.correlationIdSupplier = correlationIdGenerator; + return this; + } + + public Supplier getCorrelationIdSupplier() { + return correlationIdSupplier; + } + + public Function getReplyHandler() { + return replyHandler; + } + + /** + * Set the behavior to use when receiving replies. + *

+ * The default is to wrap the reply into a {@link com.rabbitmq.client.RpcClient.Response} + * instance. Unroutable requests will result in {@link UnroutableRpcRequestException} + * exceptions. + * + * @param replyHandler + * @return + * @see #useMandatory() + * @see #useMandatory(boolean) + */ + public RpcClientParams replyHandler(Function replyHandler) { + this.replyHandler = replyHandler; + return this; + } +} diff --git a/src/main/java/com/rabbitmq/client/RpcServer.java b/src/main/java/com/rabbitmq/client/RpcServer.java new file mode 100644 index 0000000000..b112298aa7 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/RpcServer.java @@ -0,0 +1,363 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import com.rabbitmq.utility.Utility; + +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Class which manages a request queue for a simple RPC-style service. + * The class is agnostic about the format of RPC arguments / return values. +*/ +public class RpcServer { + /** Channel we are communicating on */ + private final Channel _channel; + /** Queue to receive requests from */ + private final String _queueName; + /** Boolean controlling the exit from the mainloop. */ + private volatile boolean _mainloopRunning = true; + + /** Consumer attached to our request queue */ + private RpcConsumer _consumer; + + /** + * Creates an RpcServer listening on a temporary exclusive + * autodelete queue. + */ + public RpcServer(Channel channel) + throws IOException + { + this(channel, null); + } + + /** + * If the passed-in queue name is null, creates a server-named + * temporary exclusive autodelete queue to use; otherwise expects + * the queue to have already been declared. + */ + public RpcServer(Channel channel, String queueName) + throws IOException + { + _channel = channel; + if (queueName == null || queueName.equals("")) { + _queueName = _channel.queueDeclare().getQueue(); + } else { + _queueName = queueName; + } + _consumer = setupConsumer(); + } + + /** + * Public API - cancels the consumer, thus deleting the queue, if + * it was a temporary queue, and marks the RpcServer as closed. + * @throws IOException if an error is encountered + */ + public void close() + throws IOException + { + if (_consumer != null) { + _channel.basicCancel(_consumer.getConsumerTag()); + _consumer = null; + } + terminateMainloop(); + } + + /** + * Registers a consumer on the reply queue. + * @throws IOException if an error is encountered + * @return the newly created and registered consumer + */ + protected RpcConsumer setupConsumer() + throws IOException + { + RpcConsumer consumer = new DefaultRpcConsumer(_channel); + _channel.basicConsume(_queueName, consumer); + return consumer; + } + + /** + * Public API - main server loop. Call this to begin processing + * requests. Request processing will continue until the Channel + * (or its underlying Connection) is shut down, or until + * terminateMainloop() is called, or until the thread running the loop + * is interrupted. + * + * Note that if the mainloop is blocked waiting for a request, the + * termination flag is not checked until a request is received, so + * a good time to call terminateMainloop() is during a request + * handler. + * + * @return the exception that signalled the Channel shutdown, or null for orderly shutdown + */ + public ShutdownSignalException mainloop() + throws IOException + { + try { + while (_mainloopRunning) { + Delivery request; + try { + request = _consumer.nextDelivery(); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + _mainloopRunning = false; + continue; + } + processRequest(request); + _channel.basicAck(request.getEnvelope().getDeliveryTag(), false); + } + return null; + } catch (ShutdownSignalException sse) { + return sse; + } + } + + /** + * Call this method to terminate the mainloop. + * + * Note that if the mainloop is blocked waiting for a request, the + * termination flag is not checked until a request is received, so + * a good time to call terminateMainloop() is during a request + * handler. + */ + public void terminateMainloop() { + _mainloopRunning = false; + } + + /** + * Private API - Process a single request. Called from mainloop(). + */ + public void processRequest(Delivery request) + throws IOException + { + AMQP.BasicProperties requestProperties = request.getProperties(); + String correlationId = requestProperties.getCorrelationId(); + String replyTo = requestProperties.getReplyTo(); + if (correlationId != null && replyTo != null) + { + AMQP.BasicProperties.Builder replyPropertiesBuilder + = new AMQP.BasicProperties.Builder().correlationId(correlationId); + AMQP.BasicProperties replyProperties = preprocessReplyProperties(request, replyPropertiesBuilder); + byte[] replyBody = handleCall(request, replyProperties); + replyProperties = postprocessReplyProperties(request, replyProperties.builder()); + _channel.basicPublish("", replyTo, replyProperties, replyBody); + } else { + handleCast(request); + } + } + + /** + * Lowest-level response method. Calls + * handleCall(AMQP.BasicProperties,byte[],AMQP.BasicProperties). + */ + public byte[] handleCall(Delivery request, + AMQP.BasicProperties replyProperties) + { + return handleCall(request.getProperties(), + request.getBody(), + replyProperties); + } + + /** + * Mid-level response method. Calls + * handleCall(byte[],AMQP.BasicProperties). + */ + public byte[] handleCall(AMQP.BasicProperties requestProperties, + byte[] requestBody, + AMQP.BasicProperties replyProperties) + { + return handleCall(requestBody, replyProperties); + } + + /** + * High-level response method. Returns an empty response by + * default - override this (or other handleCall and handleCast + * methods) in subclasses. + */ + public byte[] handleCall(byte[] requestBody, + AMQP.BasicProperties replyProperties) + { + return new byte[0]; + } + + /** + * Gives a chance to set/modify reply properties before handling call. + * Note the correlationId property is already set. + * @param request the inbound message + * @param builder the reply properties builder + * @return the properties to pass in to the handling call + */ + protected AMQP.BasicProperties preprocessReplyProperties(Delivery request, AMQP.BasicProperties.Builder builder) { + return builder.build(); + } + + /** + * Gives a chance to set/modify reply properties after the handling call + * @param request the inbound message + * @param builder the reply properties builder + * @return the properties to pass in to the response message + */ + protected AMQP.BasicProperties postprocessReplyProperties(Delivery request, AMQP.BasicProperties.Builder builder) { + return builder.build(); + } + + /** + * Lowest-level handler method. Calls + * handleCast(AMQP.BasicProperties,byte[]). + */ + public void handleCast(Delivery request) + { + handleCast(request.getProperties(), request.getBody()); + } + + /** + * Mid-level handler method. Calls + * handleCast(byte[]). + */ + public void handleCast(AMQP.BasicProperties requestProperties, byte[] requestBody) + { + handleCast(requestBody); + } + + /** + * High-level handler method. Does nothing by default - override + * this (or other handleCast and handleCast methods) in + * subclasses. + */ + public void handleCast(byte[] requestBody) + { + // Does nothing. + } + + /** + * Retrieve the channel. + * @return the channel to which this server is connected + */ + public Channel getChannel() { + return _channel; + } + + /** + * Retrieve the queue name. + * @return the queue which this server is consuming from + */ + public String getQueueName() { + return _queueName; + } + + public interface RpcConsumer extends Consumer { + + Delivery nextDelivery() throws InterruptedException, ShutdownSignalException, ConsumerCancelledException; + + String getConsumerTag(); + + } + + private static class DefaultRpcConsumer extends DefaultConsumer implements RpcConsumer { + + // Marker object used to signal the queue is in shutdown mode. + // It is only there to wake up consumers. The canonical representation + // of shutting down is the presence of _shutdown. + // Invariant: This is never on _queue unless _shutdown != null. + private static final Delivery POISON = new Delivery(null, null, null); + private final BlockingQueue _queue; + // When this is non-null the queue is in shutdown mode and nextDelivery should + // throw a shutdown signal exception. + private volatile ShutdownSignalException _shutdown; + private volatile ConsumerCancelledException _cancelled; + + public DefaultRpcConsumer(Channel ch) { + this(ch, new LinkedBlockingQueue<>()); + } + + public DefaultRpcConsumer(Channel ch, BlockingQueue q) { + super(ch); + this._queue = q; + } + + @Override + public Delivery nextDelivery() throws InterruptedException, ShutdownSignalException, ConsumerCancelledException { + return handle(_queue.take()); + } + + @Override + public void handleShutdownSignal(String consumerTag, + ShutdownSignalException sig) { + _shutdown = sig; + _queue.add(POISON); + } + + @Override + public void handleCancel(String consumerTag) throws IOException { + _cancelled = new ConsumerCancelledException(); + _queue.add(POISON); + } + + @Override + public void handleDelivery(String consumerTag, + Envelope envelope, + AMQP.BasicProperties properties, + byte[] body) + throws IOException { + checkShutdown(); + this._queue.add(new Delivery(envelope, properties, body)); + } + + /** + * Check if we are in shutdown mode and if so throw an exception. + */ + private void checkShutdown() { + if (_shutdown != null) + throw Utility.fixStackTrace(_shutdown); + } + + /** + * If delivery is not POISON nor null, return it. + *

+ * If delivery, _shutdown and _cancelled are all null, return null. + *

+ * If delivery is POISON re-insert POISON into the queue and + * throw an exception if POISONed for no reason. + *

+ * Otherwise, if we are in shutdown mode or cancelled, + * throw a corresponding exception. + */ + private Delivery handle(Delivery delivery) { + if (delivery == POISON || + delivery == null && (_shutdown != null || _cancelled != null)) { + if (delivery == POISON) { + _queue.add(POISON); + if (_shutdown == null && _cancelled == null) { + throw new IllegalStateException( + "POISON in queue, but null _shutdown and null _cancelled. " + + "This should never happen, please report as a BUG"); + } + } + if (null != _shutdown) + throw Utility.fixStackTrace(_shutdown); + if (null != _cancelled) + throw Utility.fixStackTrace(_cancelled); + } + return delivery; + } + } + + +} + diff --git a/src/main/java/com/rabbitmq/client/SaslConfig.java b/src/main/java/com/rabbitmq/client/SaslConfig.java new file mode 100644 index 0000000000..9b68958d24 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/SaslConfig.java @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * This interface represents a hook to allow you to control how exactly + * a sasl client is selected during authentication. + * @see com.rabbitmq.client.ConnectionFactory + */ +public interface SaslConfig { + SaslMechanism getSaslMechanism(String[] mechanisms); +} diff --git a/src/main/java/com/rabbitmq/client/SaslMechanism.java b/src/main/java/com/rabbitmq/client/SaslMechanism.java new file mode 100644 index 0000000000..ceb210cb2f --- /dev/null +++ b/src/main/java/com/rabbitmq/client/SaslMechanism.java @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * Our own view of a SASL authentication mechanism, introduced to remove a + * dependency on javax.security.sasl. + */ +public interface SaslMechanism { + /** + * The name of this mechanism (e.g. PLAIN) + * @return the name + */ + String getName(); + + /** + * Handle one round of challenge-response + * @param challenge the challenge this round, or null on first round. + * @param username name of user + * @param password for username + * @return response + */ + LongString handleChallenge(LongString challenge, String username, String password); +} diff --git a/src/main/java/com/rabbitmq/client/ShutdownListener.java b/src/main/java/com/rabbitmq/client/ShutdownListener.java new file mode 100644 index 0000000000..755c3020ba --- /dev/null +++ b/src/main/java/com/rabbitmq/client/ShutdownListener.java @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import java.util.EventListener; + +/** + * A ShutdownListener receives information about the shutdown of connections and + * channels. Note that when a connection is shut down, its associated channels are also + * considered shut down and their ShutdownListeners will be notified (with the same cause). + * Because of this, and the fact that channel ShutdownListeners execute in the connection's + * thread, attempting to make blocking calls on a connection inside the listener will + * lead to deadlock. + * + * @see ShutdownNotifier + * @see ShutdownSignalException + */ +@FunctionalInterface +public interface ShutdownListener extends EventListener { + void shutdownCompleted(ShutdownSignalException cause); +} diff --git a/src/com/rabbitmq/client/ShutdownNotifier.java b/src/main/java/com/rabbitmq/client/ShutdownNotifier.java similarity index 57% rename from src/com/rabbitmq/client/ShutdownNotifier.java rename to src/main/java/com/rabbitmq/client/ShutdownNotifier.java index e2ce0b8838..711c86d6f2 100644 --- a/src/com/rabbitmq/client/ShutdownNotifier.java +++ b/src/main/java/com/rabbitmq/client/ShutdownNotifier.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; @@ -31,26 +30,26 @@ public interface ShutdownNotifier { * * @param listener {@link ShutdownListener} to the component */ - public void addShutdownListener(ShutdownListener listener); + void addShutdownListener(ShutdownListener listener); /** * Remove shutdown listener for the component. * * @param listener {@link ShutdownListener} to be removed */ - public void removeShutdownListener(ShutdownListener listener); + void removeShutdownListener(ShutdownListener listener); /** * Get the shutdown reason object * @return ShutdownSignalException if component is closed, null otherwise */ - public ShutdownSignalException getCloseReason(); + ShutdownSignalException getCloseReason(); /** * Protected API - notify the listeners attached to the component * @see com.rabbitmq.client.ShutdownListener */ - public void notifyListeners(); + void notifyListeners(); /** * Determine whether the component is currently open. diff --git a/src/com/rabbitmq/client/ShutdownSignalException.java b/src/main/java/com/rabbitmq/client/ShutdownSignalException.java similarity index 82% rename from src/com/rabbitmq/client/ShutdownSignalException.java rename to src/main/java/com/rabbitmq/client/ShutdownSignalException.java index c90df1ea2e..f61d913cfc 100644 --- a/src/com/rabbitmq/client/ShutdownSignalException.java +++ b/src/main/java/com/rabbitmq/client/ShutdownSignalException.java @@ -1,125 +1,125 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client; - -import com.rabbitmq.utility.SensibleClone; - -/** - * Encapsulates a shutdown condition for a connection to an AMQP broker. - * Depending on HardError when calling - * {@link com.rabbitmq.client.ShutdownSignalException#getReference()} we will - * either get a reference to the Connection or Channel instance that fired - * this exception. - */ - -public class ShutdownSignalException extends RuntimeException implements SensibleClone { - /** Default for non-checking. */ - private static final long serialVersionUID = 1L; - - /** True if the connection is shut down, or false if this signal refers to a channel */ - private final boolean _hardError; - - /** - * True if this exception is caused by explicit application - * action; false if it originated with the broker or as a result - * of detectable non-deliberate application failure - */ - private final boolean _initiatedByApplication; - - /** Possible explanation */ - private final Method _reason; - - /** Either Channel or Connection instance, depending on _hardError */ - private final Object _ref; - - /** - * Construct a ShutdownSignalException from the arguments. - * @param hardError the relevant hard error - * @param initiatedByApplication if the shutdown was client-initiated - * @param reason AMQP method describing the exception reason - * @param ref Reference to Connection or Channel that fired the signal - */ - public ShutdownSignalException(boolean hardError, - boolean initiatedByApplication, - Method reason, Object ref) - { - this(hardError, initiatedByApplication, reason, ref, "", null); - } - - /** - * Construct a ShutdownSignalException from the arguments. - * @param hardError the relevant hard error - * @param initiatedByApplication if the shutdown was client-initiated - * @param reason AMQP method describing the exception reason - * @param ref Reference to Connection or Channel that fired the signal - * @param messagePrefix prefix to add to exception message - */ - public ShutdownSignalException(boolean hardError, - boolean initiatedByApplication, - Method reason, Object ref, String messagePrefix, Throwable cause) - { - super(composeMessage(hardError, initiatedByApplication, reason, messagePrefix, cause)); - this._hardError = hardError; - this._initiatedByApplication = initiatedByApplication; - this._reason = reason; - // Depending on hardError what we got is either Connection or Channel reference - this._ref = ref; - } - - private static String composeMessage(boolean hardError, boolean initiatedByApplication, - Method reason, String messagePrefix, Throwable cause) { - final String connectionOrChannel = hardError ? "connection" : "channel"; - final String appInitiated = "clean " + connectionOrChannel + " shutdown"; - final String nonAppInitiated = connectionOrChannel + " error"; - final String explanation = initiatedByApplication ? appInitiated : nonAppInitiated; - - StringBuilder result = new StringBuilder(messagePrefix).append(explanation); - if(reason != null) { - result.append("; protocol method: ").append(reason); - } - if(cause != null) { - result.append("; cause: ").append(cause); - } - return result.toString(); - } - - /** @return true if this signals a connection error, or false if a channel error */ - public boolean isHardError() { return _hardError; } - - /** @return true if this exception was caused by explicit application - * action; false if it originated with the broker or as a result - * of detectable non-deliberate application failure - */ - public boolean isInitiatedByApplication() { return _initiatedByApplication; } - - /** @return the reason, if any */ - public Method getReason() { return _reason; } - - /** @return Reference to Connection or Channel object that fired the signal **/ - public Object getReference() { return _ref; } - - public ShutdownSignalException sensibleClone() { - try { - return (ShutdownSignalException)super.clone(); - } catch (CloneNotSupportedException e) { - // You've got to be kidding me - throw new Error(e); - } - } -} - - +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import com.rabbitmq.utility.SensibleClone; + +/** + * Encapsulates a shutdown condition for a connection to an AMQP broker. + * Depending on HardError when calling + * {@link com.rabbitmq.client.ShutdownSignalException#getReference()} we will + * either get a reference to the Connection or Channel instance that fired + * this exception. + */ + +public class ShutdownSignalException extends RuntimeException implements SensibleClone { + /** Default for non-checking. */ + private static final long serialVersionUID = 1L; + + /** True if the connection is shut down, or false if this signal refers to a channel */ + private final boolean _hardError; + + /** + * True if this exception is caused by explicit application + * action; false if it originated with the broker or as a result + * of detectable non-deliberate application failure + */ + private final boolean _initiatedByApplication; + + /** Possible explanation */ + private final Method _reason; + + /** Either Channel or Connection instance, depending on _hardError */ + private final Object _ref; + + /** + * Construct a ShutdownSignalException from the arguments. + * @param hardError the relevant hard error + * @param initiatedByApplication if the shutdown was client-initiated + * @param reason AMQP method describing the exception reason + * @param ref Reference to Connection or Channel that fired the signal + */ + public ShutdownSignalException(boolean hardError, + boolean initiatedByApplication, + Method reason, Object ref) + { + this(hardError, initiatedByApplication, reason, ref, "", null); + } + + /** + * Construct a ShutdownSignalException from the arguments. + * @param hardError the relevant hard error + * @param initiatedByApplication if the shutdown was client-initiated + * @param reason AMQP method describing the exception reason + * @param ref Reference to Connection or Channel that fired the signal + * @param messagePrefix prefix to add to exception message + */ + public ShutdownSignalException(boolean hardError, + boolean initiatedByApplication, + Method reason, Object ref, String messagePrefix, Throwable cause) + { + super(composeMessage(hardError, initiatedByApplication, reason, messagePrefix, cause)); + this._hardError = hardError; + this._initiatedByApplication = initiatedByApplication; + this._reason = reason; + // Depending on hardError what we got is either Connection or Channel reference + this._ref = ref; + } + + private static String composeMessage(boolean hardError, boolean initiatedByApplication, + Method reason, String messagePrefix, Throwable cause) { + final String connectionOrChannel = hardError ? "connection" : "channel"; + final String appInitiated = "clean " + connectionOrChannel + " shutdown"; + final String nonAppInitiated = connectionOrChannel + " error"; + final String explanation = initiatedByApplication ? appInitiated : nonAppInitiated; + + StringBuilder result = new StringBuilder(messagePrefix).append(explanation); + if(reason != null) { + result.append("; protocol method: ").append(reason); + } + if(cause != null) { + result.append("; cause: ").append(cause); + } + return result.toString(); + } + + /** @return true if this signals a connection error, or false if a channel error */ + public boolean isHardError() { return _hardError; } + + /** @return true if this exception was caused by explicit application + * action; false if it originated with the broker or as a result + * of detectable non-deliberate application failure + */ + public boolean isInitiatedByApplication() { return _initiatedByApplication; } + + /** @return the reason, if any */ + public Method getReason() { return _reason; } + + /** @return Reference to Connection or Channel object that fired the signal **/ + public Object getReference() { return _ref; } + + @Override + public ShutdownSignalException sensibleClone() { + try { + return (ShutdownSignalException)super.clone(); + } catch (CloneNotSupportedException e) { + // You've got to be kidding me + throw new RuntimeException(e); + } + } +} + + diff --git a/src/main/java/com/rabbitmq/client/SocketChannelConfigurator.java b/src/main/java/com/rabbitmq/client/SocketChannelConfigurator.java new file mode 100644 index 0000000000..69dc2ef0b2 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/SocketChannelConfigurator.java @@ -0,0 +1,45 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.IOException; +import java.nio.channels.SocketChannel; +import java.util.Objects; + +@FunctionalInterface +public interface SocketChannelConfigurator { + + /** + * Provides a hook to insert custom configuration of the {@link SocketChannel}s + * used to connect to an AMQP server before they connect. + */ + void configure(SocketChannel socketChannel) throws IOException; + + /** + * Returns a composed configurator that performs, in sequence, this + * operation followed by the {@code after} operation. + * + * @param after the operation to perform after this operation + * @return a composed configurator that performs in sequence this + * operation followed by the {@code after} operation + * @throws NullPointerException if {@code after} is null + */ + default SocketChannelConfigurator andThen(SocketChannelConfigurator after) { + Objects.requireNonNull(after); + return t -> { configure(t); after.configure(t); }; + } + +} diff --git a/src/main/java/com/rabbitmq/client/SocketChannelConfigurators.java b/src/main/java/com/rabbitmq/client/SocketChannelConfigurators.java new file mode 100644 index 0000000000..95d96c4fad --- /dev/null +++ b/src/main/java/com/rabbitmq/client/SocketChannelConfigurators.java @@ -0,0 +1,111 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * Ready-to-use instances and builder for {@link SocketChannelConfigurator}. + *

+ * Note {@link SocketChannelConfigurator}s can be combined with + * {@link SocketChannelConfigurator#andThen(SocketChannelConfigurator)}. + * + * @since 5.4.0 + */ +public abstract class SocketChannelConfigurators { + + /** + * Disable Nagle's algorithm. + */ + public static final SocketChannelConfigurator DISABLE_NAGLE_ALGORITHM = + socketChannel -> SocketConfigurators.DISABLE_NAGLE_ALGORITHM.configure(socketChannel.socket()); + + /** + * Default {@link SocketChannelConfigurator} that disables Nagle's algorithm. + */ + public static final SocketChannelConfigurator DEFAULT = DISABLE_NAGLE_ALGORITHM; + + /** + * The default {@link SocketChannelConfigurator} that disables Nagle's algorithm. + * + * @return + */ + public static SocketChannelConfigurator defaultConfigurator() { + return DEFAULT; + } + + /** + * {@link SocketChannelConfigurator} that disables Nagle's algorithm. + * + * @return + */ + public static SocketChannelConfigurator disableNagleAlgorithm() { + return DISABLE_NAGLE_ALGORITHM; + } + + /** + * Builder to configure and creates a {@link SocketChannelConfigurator} instance. + * + * @return + */ + public static SocketChannelConfigurators.Builder builder() { + return new SocketChannelConfigurators.Builder(); + } + + public static class Builder { + + private SocketChannelConfigurator configurator = channel -> { + }; + + /** + * Set default configuration. + * + * @return + */ + public Builder defaultConfigurator() { + configurator = configurator.andThen(DEFAULT); + return this; + } + + /** + * Disable Nagle's Algorithm. + * + * @return + */ + public Builder disableNagleAlgorithm() { + configurator = configurator.andThen(DISABLE_NAGLE_ALGORITHM); + return this; + } + + /** + * Add an extra configuration step. + * + * @param extraConfiguration + * @return + */ + public Builder add(SocketChannelConfigurator extraConfiguration) { + configurator = configurator.andThen(extraConfiguration); + return this; + } + + /** + * Return the configured {@link SocketConfigurator}. + * + * @return + */ + public SocketChannelConfigurator build() { + return configurator; + } + } +} diff --git a/src/main/java/com/rabbitmq/client/SocketConfigurator.java b/src/main/java/com/rabbitmq/client/SocketConfigurator.java new file mode 100644 index 0000000000..e0b7bd355f --- /dev/null +++ b/src/main/java/com/rabbitmq/client/SocketConfigurator.java @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.IOException; +import java.net.Socket; +import java.util.Objects; + +@FunctionalInterface +public interface SocketConfigurator { + + /** + * Provides a hook to insert custom configuration of the sockets + * used to connect to an AMQP server before they connect. + */ + void configure(Socket socket) throws IOException; + + /** + * Returns a composed configurator that performs, in sequence, this + * operation followed by the {@code after} operation. + * + * @param after the operation to perform after this operation + * @return a composed configurator that performs in sequence this + * operation followed by the {@code after} operation + * @throws NullPointerException if {@code after} is null + */ + default SocketConfigurator andThen(SocketConfigurator after) { + Objects.requireNonNull(after); + return t -> { + configure(t); + after.configure(t); + }; + } +} diff --git a/src/main/java/com/rabbitmq/client/SocketConfigurators.java b/src/main/java/com/rabbitmq/client/SocketConfigurators.java new file mode 100644 index 0000000000..944d9a4611 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/SocketConfigurators.java @@ -0,0 +1,153 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; + +/** + * Ready-to-use instances and builder for {@link SocketConfigurator}. + *

+ * Note {@link SocketConfigurator}s can be combined with + * {@link SocketConfigurator#andThen(SocketConfigurator)}. + * + * @since 5.4.0 + */ +public abstract class SocketConfigurators { + + /** + * Disable Nagle's algorithm. + */ + public static final SocketConfigurator DISABLE_NAGLE_ALGORITHM = socket -> socket.setTcpNoDelay(true); + + /** + * Default {@link SocketConfigurator} that disables Nagle's algorithm. + */ + public static final SocketConfigurator DEFAULT = DISABLE_NAGLE_ALGORITHM; + + /** + * Enable server hostname validation for TLS connections. + */ + public static final SocketConfigurator ENABLE_HOSTNAME_VERIFICATION = socket -> { + if (socket instanceof SSLSocket) { + SSLSocket sslSocket = (SSLSocket) socket; + SSLParameters sslParameters = enableHostnameVerification(sslSocket.getSSLParameters()); + sslSocket.setSSLParameters(sslParameters); + } + }; + + static SSLParameters enableHostnameVerification(SSLParameters sslParameters) { + if (sslParameters == null) { + sslParameters = new SSLParameters(); + } + // It says HTTPS but works also for any TCP connection. + // It checks SAN (Subject Alternative Name) as well as CN. + sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); + return sslParameters; + } + + /** + * The default {@link SocketConfigurator} that disables Nagle's algorithm. + * + * @return Default configurator: only disables Nagle's algirithm + */ + public static SocketConfigurator defaultConfigurator() { + return DEFAULT; + } + + /** + * {@link SocketConfigurator} that disables Nagle's algorithm. + * + * @return A composable configurator that diasbles Nagle's algirithm + */ + public static SocketConfigurator disableNagleAlgorithm() { + return DISABLE_NAGLE_ALGORITHM; + } + + /** + * {@link SocketConfigurator} that enable server hostname verification for TLS connections. + * + * @return A composable configurator that enables peer hostname verification + */ + public static SocketConfigurator enableHostnameVerification() { + return ENABLE_HOSTNAME_VERIFICATION; + } + + /** + * Builder to configure and creates a {@link SocketConfigurator} instance. + * + * @return + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private SocketConfigurator configurator = socket -> { + }; + + /** + * Set default configuration. + * + * @return this + */ + public Builder defaultConfigurator() { + configurator = configurator.andThen(DEFAULT); + return this; + } + + /** + * Disable Nagle's Algorithm. + * + * @return this + */ + public Builder disableNagleAlgorithm() { + configurator = configurator.andThen(DISABLE_NAGLE_ALGORITHM); + return this; + } + + /** + * Enable server hostname verification for TLS connections. + * + * @return this + */ + public Builder enableHostnameVerification() { + configurator = configurator.andThen(ENABLE_HOSTNAME_VERIFICATION); + return this; + } + + /** + * Add an extra configuration step. + * + * @param extraConfiguration + * @return this + */ + public Builder add(SocketConfigurator extraConfiguration) { + configurator = configurator.andThen(extraConfiguration); + return this; + } + + /** + * Return the configured {@link SocketConfigurator}. + * + * @return the final configurator + */ + public SocketConfigurator build() { + return configurator; + } + } +} diff --git a/src/main/java/com/rabbitmq/client/SslContextFactory.java b/src/main/java/com/rabbitmq/client/SslContextFactory.java new file mode 100644 index 0000000000..c012111970 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/SslContextFactory.java @@ -0,0 +1,37 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import javax.net.ssl.SSLContext; + +/** + * A factory to create {@link SSLContext}s. + * + * @see ConnectionFactory#setSslContextFactory(SslContextFactory) + * @since 5.0.0 + */ +public interface SslContextFactory { + + /** + * Create a {@link SSLContext} for a given name. + * The name is typically the name of the connection. + * @param name name of the connection the SSLContext is used for + * @return the SSLContext for this name + * @see ConnectionFactory#newConnection(String) + */ + SSLContext create(String name); + +} diff --git a/src/main/java/com/rabbitmq/client/SslEngineConfigurator.java b/src/main/java/com/rabbitmq/client/SslEngineConfigurator.java new file mode 100644 index 0000000000..78b2b2eae9 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/SslEngineConfigurator.java @@ -0,0 +1,46 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import javax.net.ssl.SSLEngine; +import java.io.IOException; +import java.util.Objects; + +@FunctionalInterface +public interface SslEngineConfigurator { + + /** + * Provides a hook to insert custom configuration of the {@link SSLEngine}s + * used to connect to an AMQP server before they connect. + * Note this is used only when NIO are in use. + */ + void configure(SSLEngine sslEngine) throws IOException; + + /** + * Returns a composed configurator that performs, in sequence, this + * operation followed by the {@code after} operation. + * + * @param after the operation to perform after this operation + * @return a composed configurator that performs in sequence this + * operation followed by the {@code after} operation + * @throws NullPointerException if {@code after} is null + */ + default SslEngineConfigurator andThen(SslEngineConfigurator after) { + Objects.requireNonNull(after); + return t -> { configure(t); after.configure(t); }; + } + +} diff --git a/src/main/java/com/rabbitmq/client/SslEngineConfigurators.java b/src/main/java/com/rabbitmq/client/SslEngineConfigurators.java new file mode 100644 index 0000000000..929fd507d4 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/SslEngineConfigurators.java @@ -0,0 +1,116 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import javax.net.ssl.SSLParameters; + +/** + * Ready-to-use instances and builder for {@link SslEngineConfigurator}s. + *

+ * Note {@link SslEngineConfigurator}s can be combined with + * {@link SslEngineConfigurator#andThen(SslEngineConfigurator)}. + * + * @since 5.4.0 + */ +public abstract class SslEngineConfigurators { + + /** + * Default {@link SslEngineConfigurator}, does nothing. + */ + public static final SslEngineConfigurator DEFAULT = sslEngine -> { + }; + + /** + * {@link SslEngineConfigurator} that enables server hostname verification. + */ + public static final SslEngineConfigurator ENABLE_HOSTNAME_VERIFICATION = sslEngine -> { + SSLParameters sslParameters = SocketConfigurators.enableHostnameVerification(sslEngine.getSSLParameters()); + sslEngine.setSSLParameters(sslParameters); + }; + + /** + * Default {@link SslEngineConfigurator}, does nothing. + * + * @return + */ + public static SslEngineConfigurator defaultConfigurator() { + return DEFAULT; + } + + /** + * {@link SslEngineConfigurator} that enables server hostname verification. + * + * @return + */ + public static SslEngineConfigurator enableHostnameVerification() { + return ENABLE_HOSTNAME_VERIFICATION; + } + + /** + * Builder to configure and creates a {@link SslEngineConfigurator} instance. + * + * @return + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private SslEngineConfigurator configurator = channel -> { + }; + + /** + * Set default configuration (no op). + * + * @return + */ + public Builder defaultConfigurator() { + configurator = configurator.andThen(DEFAULT); + return this; + } + + /** + * Enables server hostname verification. + * + * @return + */ + public Builder enableHostnameVerification() { + configurator = configurator.andThen(ENABLE_HOSTNAME_VERIFICATION); + return this; + } + + /** + * Add extra configuration step. + * + * @param extraConfiguration + * @return + */ + public Builder add(SslEngineConfigurator extraConfiguration) { + configurator = configurator.andThen(extraConfiguration); + return this; + } + + /** + * Return the configured {@link SslEngineConfigurator}. + * + * @return + */ + public SslEngineConfigurator build() { + return configurator; + } + } +} diff --git a/src/com/rabbitmq/client/StringRpcServer.java b/src/main/java/com/rabbitmq/client/StringRpcServer.java similarity index 72% rename from src/com/rabbitmq/client/StringRpcServer.java rename to src/main/java/com/rabbitmq/client/StringRpcServer.java index 9ea120c6ab..eae700ee8e 100644 --- a/src/com/rabbitmq/client/StringRpcServer.java +++ b/src/main/java/com/rabbitmq/client/StringRpcServer.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; @@ -36,6 +35,7 @@ public StringRpcServer(Channel channel, String queueName) throws IOException * handleStringCall. If UTF-8 is not understood by this JVM, falls * back to the platform default. */ + @Override @SuppressWarnings("unused") public byte[] handleCall(byte[] requestBody, AMQP.BasicProperties replyProperties) { @@ -74,6 +74,7 @@ public String handleStringCall(String request) * handleStringCast. If requestBody cannot be interpreted as UTF-8 * tries the platform default. */ + @Override public void handleCast(byte[] requestBody) { try { diff --git a/src/main/java/com/rabbitmq/client/TopologyRecoveryException.java b/src/main/java/com/rabbitmq/client/TopologyRecoveryException.java new file mode 100644 index 0000000000..712315bc6f --- /dev/null +++ b/src/main/java/com/rabbitmq/client/TopologyRecoveryException.java @@ -0,0 +1,42 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import com.rabbitmq.client.impl.recovery.RecordedEntity; + +/** + * Indicates an exception thrown during topology recovery. + * + * @see com.rabbitmq.client.ConnectionFactory#setTopologyRecoveryEnabled(boolean) + * @since 3.3.0 + */ +public class TopologyRecoveryException extends Exception { + + private final RecordedEntity recordedEntity; + + public TopologyRecoveryException(String message, Throwable cause) { + this(message, cause, null); + } + + public TopologyRecoveryException(String message, Throwable cause, final RecordedEntity recordedEntity) { + super(message, cause); + this.recordedEntity = recordedEntity; + } + + public RecordedEntity getRecordedEntity() { + return recordedEntity; + } +} diff --git a/src/main/java/com/rabbitmq/client/TrafficListener.java b/src/main/java/com/rabbitmq/client/TrafficListener.java new file mode 100644 index 0000000000..10e13a6a97 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/TrafficListener.java @@ -0,0 +1,40 @@ +package com.rabbitmq.client; + +/** + * Contract to log outbound and inbound {@link Command}s. + * + * @see ConnectionFactory#setTrafficListener(TrafficListener) + * @since 5.5.0 + */ +public interface TrafficListener { + + /** + * No-op {@link TrafficListener}. + */ + TrafficListener NO_OP = new TrafficListener() { + + @Override + public void write(Command outboundCommand) { + + } + + @Override + public void read(Command inboundCommand) { + + } + }; + + /** + * Notified for each outbound {@link Command}. + * + * @param outboundCommand + */ + void write(Command outboundCommand); + + /** + * Notified for each inbound {@link Command}. + * + * @param inboundCommand + */ + void read(Command inboundCommand); +} diff --git a/src/main/java/com/rabbitmq/client/TrustEverythingTrustManager.java b/src/main/java/com/rabbitmq/client/TrustEverythingTrustManager.java new file mode 100644 index 0000000000..644ed4b121 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/TrustEverythingTrustManager.java @@ -0,0 +1,65 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import org.slf4j.LoggerFactory; + +import javax.net.ssl.X509TrustManager; +import java.security.cert.X509Certificate; + +/** + * Convenience class providing a default implementation of {@link javax.net.ssl.X509TrustManager}. + * Trusts every single certificate presented to it. This implementation does not perform peer + * verification and provides no protection against Man-in-the-Middle (MITM) attacks and therefore + * only suitable for some development and QA environments. + */ +public class TrustEverythingTrustManager implements X509TrustManager { + + public TrustEverythingTrustManager() { + LoggerFactory.getLogger(TrustEverythingTrustManager.class).warn( + "SECURITY ALERT: this trust manager trusts every certificate, effectively disabling peer verification. " + + "This is convenient for local development but offers no protection against man-in-the-middle attacks. " + + "Please see https://www.rabbitmq.com/ssl.html to learn more about peer certificate verification." + ); + } + + /** + * Doesn't even bother looking at its arguments, simply returns, + * which makes the check succeed. + */ + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + // Do nothing. + } + + /** + * Doesn't even bother looking at its arguments, simply returns, + * which makes the check succeed. + */ + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + // Do nothing. + } + + /** + * Always returns an empty array of X509Certificates. + */ + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} diff --git a/src/main/java/com/rabbitmq/client/UnblockedCallback.java b/src/main/java/com/rabbitmq/client/UnblockedCallback.java new file mode 100644 index 0000000000..4421ba0d81 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/UnblockedCallback.java @@ -0,0 +1,31 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import java.io.IOException; + +/** + * Implement this interface in order to be notified of connection unblock events. + * Prefer it over {@link BlockedListener} for a lambda-oriented syntax. + * @see BlockedListener + * @see BlockedCallback + */ +@FunctionalInterface +public interface UnblockedCallback { + + void handle() throws IOException; + +} diff --git a/src/main/java/com/rabbitmq/client/UnexpectedFrameError.java b/src/main/java/com/rabbitmq/client/UnexpectedFrameError.java new file mode 100644 index 0000000000..f8cecdaadb --- /dev/null +++ b/src/main/java/com/rabbitmq/client/UnexpectedFrameError.java @@ -0,0 +1,46 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +import com.rabbitmq.client.impl.Frame; + +/** + * Thrown when the command parser hits an unexpected frame type. + */ +public class UnexpectedFrameError extends RuntimeException { + private static final long serialVersionUID = 1L; + private final Frame _frame; + private final int _expectedFrameType; + + public UnexpectedFrameError(Frame frame, int expectedFrameType) { + super("Received frame: " + frame + ", expected type " + expectedFrameType); + _frame = frame; + _expectedFrameType = expectedFrameType; + } + + public static long getSerialVersionUID() { + return serialVersionUID; + } + + public Frame getReceivedFrame() { + return _frame; + } + + public int getExpectedFrameType() { + return _expectedFrameType; + } +} diff --git a/src/com/rabbitmq/client/UnexpectedMethodError.java b/src/main/java/com/rabbitmq/client/UnexpectedMethodError.java similarity index 51% rename from src/com/rabbitmq/client/UnexpectedMethodError.java rename to src/main/java/com/rabbitmq/client/UnexpectedMethodError.java index a474d2bde3..3c5f094172 100644 --- a/src/com/rabbitmq/client/UnexpectedMethodError.java +++ b/src/main/java/com/rabbitmq/client/UnexpectedMethodError.java @@ -1,55 +1,55 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client; - -/** - * Indicates that a {@link Method} object was supplied that was not - * expected. For instance, {@link Channel#basicGet} throws this if it - * receives anything other than {@link AMQP.Basic.GetOk} or - * {@link AMQP.Basic.GetEmpty}, and the - * {@link com.rabbitmq.client.impl.AMQImpl.DefaultMethodVisitor DefaultMethodVisitor} - * throws this as the action within each visitor case. - */ -public class UnexpectedMethodError extends Error { - private static final long serialVersionUID = 1L; - private final Method _method; - - /** - * Construct an UnexpecteMethodError with the given method parameter - * @param method the unexpected method - */ - public UnexpectedMethodError(Method method) { - _method = method; - } - - /** - * Return a string representation of this error. - * @return a string describing the error - */ - public String toString() { - return super.toString() + ": " + _method; - } - - /** - * Return the wrapped method. - * @return the method whose appearance was "unexpected" and was deemed an error - */ - public Method getMethod() { - return _method; - } -} +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client; + +/** + * Indicates that a {@link Method} object was supplied that was not + * expected. For instance, {@link Channel#basicGet} throws this if it + * receives anything other than {@link AMQP.Basic.GetOk} or + * {@link AMQP.Basic.GetEmpty}, and the + * {@link com.rabbitmq.client.impl.AMQImpl.DefaultMethodVisitor DefaultMethodVisitor} + * throws this as the action within each visitor case. + */ +public class UnexpectedMethodError extends RuntimeException { + private static final long serialVersionUID = 1L; + private final Method _method; + + /** + * Construct an UnexpectedMethodError with the given method parameter + * @param method the unexpected method + */ + public UnexpectedMethodError(Method method) { + _method = method; + } + + /** + * Return a string representation of this error. + * @return a string describing the error + */ + @Override + public String toString() { + return super.toString() + ": " + _method; + } + + /** + * Return the wrapped method. + * @return the method whose appearance was "unexpected" and was deemed an error + */ + public Method getMethod() { + return _method; + } +} diff --git a/src/com/rabbitmq/client/UnknownClassOrMethodId.java b/src/main/java/com/rabbitmq/client/UnknownClassOrMethodId.java similarity index 52% rename from src/com/rabbitmq/client/UnknownClassOrMethodId.java rename to src/main/java/com/rabbitmq/client/UnknownClassOrMethodId.java index 058d3f6b89..f178e04c95 100644 --- a/src/com/rabbitmq/client/UnknownClassOrMethodId.java +++ b/src/main/java/com/rabbitmq/client/UnknownClassOrMethodId.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; @@ -35,6 +34,7 @@ public UnknownClassOrMethodId(int classId, int methodId) { this.classId = classId; this.methodId = methodId; } + @Override public String toString() { if (this.methodId == NO_METHOD_ID) { return super.toString() + "<" + classId + ">"; diff --git a/src/main/java/com/rabbitmq/client/UnroutableRpcRequestException.java b/src/main/java/com/rabbitmq/client/UnroutableRpcRequestException.java new file mode 100644 index 0000000000..f040c91f65 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/UnroutableRpcRequestException.java @@ -0,0 +1,44 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +/** + * Exception thrown when a RPC request isn't routed to any queue. + *

+ * The {@link RpcClient} must be configured with the mandatory + * flag set to true with {@link RpcClientParams#useMandatory()}. + * + * @see RpcClientParams#useMandatory() + * @see RpcClient#RpcClient(RpcClientParams) + * @since 5.6.0 + */ +public class UnroutableRpcRequestException extends RuntimeException { + + private final Return returnMessage; + + public UnroutableRpcRequestException(Return returnMessage) { + this.returnMessage = returnMessage; + } + + /** + * The returned message. + * + * @return + */ + public Return getReturnMessage() { + return returnMessage; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/AMQBasicProperties.java b/src/main/java/com/rabbitmq/client/impl/AMQBasicProperties.java new file mode 100644 index 0000000000..0ac0e3fc41 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/AMQBasicProperties.java @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import java.io.DataInputStream; +import java.io.IOException; + +import com.rabbitmq.client.BasicProperties; + +public abstract class AMQBasicProperties + extends AMQContentHeader implements BasicProperties { + + protected AMQBasicProperties() { + + } + + protected AMQBasicProperties(DataInputStream in) throws IOException { + super(in); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/AMQChannel.java b/src/main/java/com/rabbitmq/client/impl/AMQChannel.java new file mode 100644 index 0000000000..067a32dceb --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/AMQChannel.java @@ -0,0 +1,638 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.AMQP.Basic; +import com.rabbitmq.client.AMQP.Confirm; +import com.rabbitmq.client.AMQP.Exchange; +import com.rabbitmq.client.AMQP.Queue; +import com.rabbitmq.client.AMQP.Tx; +import com.rabbitmq.client.Method; +import com.rabbitmq.client.observation.ObservationCollector; +import com.rabbitmq.utility.BlockingValueOrException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +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.Supplier; + +/** + * Base class modelling an AMQ channel. Subclasses implement + * {@link com.rabbitmq.client.Channel#close} and + * {@link #processAsync processAsync()}, and may choose to override + * {@link #processShutdownSignal processShutdownSignal()} and + * {@link #rpc rpc()}. + * + * @see ChannelN + * @see Connection + */ +public abstract class AMQChannel extends ShutdownNotifierComponent { + + private static final Logger LOGGER = LoggerFactory.getLogger(AMQChannel.class); + + static final int NO_RPC_TIMEOUT = 0; + + /** + * Protected; used instead of synchronizing on the channel itself, + * so that clients can themselves use the channel to synchronize + * on. + */ + protected final Lock _channelLock = new ReentrantLock(); + protected final Condition _channelLockCondition = _channelLock.newCondition(); + + /** The connection this channel is associated with. */ + private final AMQConnection _connection; + + /** This channel's channel number. */ + private final int _channelNumber; + + /** Command being assembled */ + private AMQCommand _command; + + /** The current outstanding RPC request, if any. (Could become a queue in future.) */ + private RpcWrapper _activeRpc = null; + + /** Whether transmission of content-bearing methods should be blocked */ + volatile boolean _blockContent = false; + + /** Timeout for RPC calls */ + final int _rpcTimeout; + + private final boolean _checkRpcResponseType; + + private final TrafficListener _trafficListener; + private final int maxInboundMessageBodySize; + + private final ObservationCollector.ConnectionInfo connectionInfo; + + /** + * Construct a channel on the given connection, with the given channel number. + * @param connection the underlying connection for this channel + * @param channelNumber the allocated reference number for this channel + */ + public AMQChannel(AMQConnection connection, int channelNumber) { + this._connection = connection; + this._channelNumber = channelNumber; + if(connection.getChannelRpcTimeout() < 0) { + throw new IllegalArgumentException("Continuation timeout on RPC calls cannot be less than 0"); + } + this._rpcTimeout = connection.getChannelRpcTimeout(); + this._checkRpcResponseType = connection.willCheckRpcResponseType(); + this._trafficListener = connection.getTrafficListener(); + this.maxInboundMessageBodySize = connection.getMaxInboundMessageBodySize(); + this._command = new AMQCommand(this.maxInboundMessageBodySize); + this.connectionInfo = connection.connectionInfo(); + } + + /** + * Public API - Retrieves this channel's channel number. + * @return the channel number + */ + public int getChannelNumber() { + return _channelNumber; + } + + /** + * Private API - When the Connection receives a Frame for this + * channel, it passes it to this method. + * @param frame the incoming frame + * @throws IOException if an error is encountered + */ + void handleFrame(Frame frame) throws IOException { + AMQCommand command = _command; + if (command.handleFrame(frame)) { // a complete command has rolled off the assembly line + _command = new AMQCommand(this.maxInboundMessageBodySize); // prepare for the next one + handleCompleteInboundCommand(command); + } + } + + /** + * Placeholder until we address bug 15786 (implementing a proper exception hierarchy). + * In the meantime, this at least won't throw away any information from the wrapped exception. + * @param ex the exception to wrap + * @return the wrapped exception + */ + public static IOException wrap(ShutdownSignalException ex) { + return wrap(ex, null); + } + + public static IOException wrap(ShutdownSignalException ex, String message) { + return new IOException(message, ex); + } + + /** + * Placeholder until we address bug 15786 (implementing a proper exception hierarchy). + */ + public AMQCommand exnWrappingRpc(Method m) + throws IOException + { + try { + return privateRpc(m); + } catch (AlreadyClosedException ace) { + // Do not wrap it since it means that connection/channel + // was closed in some action in the past + throw ace; + } catch (ShutdownSignalException ex) { + throw wrap(ex); + } + } + + CompletableFuture exnWrappingAsyncRpc(Method m) + throws IOException + { + try { + return privateAsyncRpc(m); + } catch (AlreadyClosedException ace) { + // Do not wrap it since it means that connection/channel + // was closed in some action in the past + throw ace; + } catch (ShutdownSignalException ex) { + throw wrap(ex); + } + } + + /** + * Private API - handle a command which has been assembled + * @throws IOException if there's any problem + * + * @param command the incoming command + * @throws IOException when operation is interrupted by an I/O exception + */ + public void handleCompleteInboundCommand(AMQCommand command) throws IOException { + // First, offer the command to the asynchronous-command + // handling mechanism, which gets to act as a filter on the + // incoming command stream. If processAsync() returns true, + // the command has been dealt with by the filter and so should + // not be processed further. It will return true for + // asynchronous commands (deliveries/returns/other events), + // and false for commands that should be passed on to some + // waiting RPC continuation. + this._trafficListener.read(command); + if (!processAsync(command)) { + // The filter decided not to handle/consume the command, + // so it must be a response to an earlier RPC. + + if (_checkRpcResponseType) { + _channelLock.lock(); + try { + // check if this reply command is intended for the current waiting request before calling nextOutstandingRpc() + if (_activeRpc != null && !_activeRpc.canHandleReply(command)) { + // this reply command is not intended for the current waiting request + // most likely a previous request timed out and this command is the reply for that. + // Throw this reply command away so we don't stop the current request from waiting for its reply + return; + } + } finally { + _channelLock.unlock(); + } + } + final RpcWrapper nextOutstandingRpc = nextOutstandingRpc(); + // the outstanding RPC can be null when calling Channel#asyncRpc + if(nextOutstandingRpc != null) { + nextOutstandingRpc.complete(command); + markRpcFinished(); + } + } + } + + public void enqueueRpc(RpcContinuation k) + { + doEnqueueRpc(() -> new RpcContinuationRpcWrapper(k)); + } + + private void enqueueAsyncRpc(Method method, CompletableFuture future) { + doEnqueueRpc(() -> new CompletableFutureRpcWrapper(method, future)); + } + + private void doEnqueueRpc(Supplier rpcWrapperSupplier) { + _channelLock.lock(); + try { + boolean waitClearedInterruptStatus = false; + while (_activeRpc != null) { + try { + _channelLockCondition.await(); + } catch (InterruptedException e) { //NOSONAR + waitClearedInterruptStatus = true; + // No Sonar: we re-interrupt the thread later + } + } + if (waitClearedInterruptStatus) { + Thread.currentThread().interrupt(); + } + _activeRpc = rpcWrapperSupplier.get(); + } finally { + _channelLock.unlock(); + } + } + + boolean isOutstandingRpc() + { + _channelLock.lock(); + try { + return (_activeRpc != null); + } finally { + _channelLock.unlock(); + } + } + + public RpcWrapper nextOutstandingRpc() + { + _channelLock.lock(); + try { + RpcWrapper result = _activeRpc; + _activeRpc = null; + _channelLockCondition.signalAll(); + return result; + } finally { + _channelLock.unlock(); + } + } + + protected void markRpcFinished() { + // no-op + } + + private void ensureIsOpen() + throws AlreadyClosedException + { + if (!isOpen()) { + throw new AlreadyClosedException(getCloseReason()); + } + } + + /** + * Protected API - sends a {@link Method} to the broker and waits for the + * next in-bound Command from the broker: only for use from + * non-connection-MainLoop threads! + */ + public AMQCommand rpc(Method m) + throws IOException, ShutdownSignalException + { + return privateRpc(m); + } + + public AMQCommand rpc(Method m, int timeout) + throws IOException, ShutdownSignalException, TimeoutException { + return privateRpc(m, timeout); + } + + private AMQCommand privateRpc(Method m) + throws IOException, ShutdownSignalException + { + SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation(m); + rpc(m, k); + // At this point, the request method has been sent, and we + // should wait for the reply to arrive. + // + // Calling getReply() on the continuation puts us to sleep + // until the connection's reader-thread throws the reply over + // the fence or the RPC times out (if enabled) + if(_rpcTimeout == NO_RPC_TIMEOUT) { + return k.getReply(); + } else { + try { + return k.getReply(_rpcTimeout); + } catch (TimeoutException e) { + throw wrapTimeoutException(m, e); + } + } + } + + private void cleanRpcChannelState() { + try { + // clean RPC channel state + nextOutstandingRpc(); + markRpcFinished(); + } catch (Exception ex) { + LOGGER.warn("Error while cleaning timed out channel RPC: {}", ex.getMessage()); + } + } + + /** Cleans RPC channel state after a timeout and wraps the TimeoutException in a ChannelContinuationTimeoutException */ + ChannelContinuationTimeoutException wrapTimeoutException(final Method m, final TimeoutException e) { + cleanRpcChannelState(); + return new ChannelContinuationTimeoutException(e, this, this._channelNumber, m); + } + + private CompletableFuture privateAsyncRpc(Method m) + throws IOException, ShutdownSignalException + { + CompletableFuture future = new CompletableFuture<>(); + asyncRpc(m, future); + return future; + } + + private AMQCommand privateRpc(Method m, int timeout) + throws IOException, ShutdownSignalException, TimeoutException { + SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation(m); + rpc(m, k); + + try { + return k.getReply(timeout); + } catch (TimeoutException e) { + cleanRpcChannelState(); + throw e; + } + } + + public void rpc(Method m, RpcContinuation k) + throws IOException + { + _channelLock.lock(); + try { + ensureIsOpen(); + quiescingRpc(m, k); + } finally { + _channelLock.unlock(); + } + } + + void quiescingRpc(Method m, RpcContinuation k) + throws IOException + { + _channelLock.lock(); + try { + enqueueRpc(k); + quiescingTransmit(m); + } finally { + _channelLock.unlock(); + } + } + + private void asyncRpc(Method m, CompletableFuture future) + throws IOException + { + _channelLock.lock(); + try { + ensureIsOpen(); + quiescingAsyncRpc(m, future); + } finally { + _channelLock.unlock(); + } + } + + private void quiescingAsyncRpc(Method m, CompletableFuture future) + throws IOException + { + _channelLock.lock(); + try { + enqueueAsyncRpc(m, future); + quiescingTransmit(m); + } finally { + _channelLock.unlock(); + } + } + + /** + * Protected API - called by nextCommand to check possibly handle an incoming Command before it is returned to the caller of nextCommand. If this method + * returns true, the command is considered handled and is not passed back to nextCommand's caller; if it returns false, nextCommand returns the command as + * usual. This is used in subclasses to implement handling of Basic.Return and Basic.Deliver messages, as well as Channel.Close and Connection.Close. + * @param command the command to handle asynchronously + * @return true if we handled the command; otherwise the caller should consider it "unhandled" + */ + public abstract boolean processAsync(Command command) throws IOException; + + @Override public String toString() { + return "AMQChannel(" + _connection + "," + _channelNumber + ")"; + } + + /** + * Protected API - respond, in the driver thread, to a {@link ShutdownSignalException}. + * @param signal the signal to handle + * @param ignoreClosed the flag indicating whether to ignore the AlreadyClosedException + * thrown when the channel is already closed + * @param notifyRpc the flag indicating whether any remaining rpc continuation should be + * notified with the given signal + */ + public void processShutdownSignal(ShutdownSignalException signal, + boolean ignoreClosed, + boolean notifyRpc) { + try { + _channelLock.lock(); + try { + if (!setShutdownCauseIfOpen(signal)) { + if (!ignoreClosed) + throw new AlreadyClosedException(getCloseReason()); + } + + _channelLockCondition.signalAll(); + } finally { + _channelLock.unlock(); + } + } finally { + if (notifyRpc) + notifyOutstandingRpc(signal); + } + } + + void notifyOutstandingRpc(ShutdownSignalException signal) { + RpcWrapper k = nextOutstandingRpc(); + if (k != null) { + k.shutdown(signal); + } + } + + public void transmit(Method m) throws IOException { + _channelLock.lock(); + try { + transmit(new AMQCommand(m)); + } finally { + _channelLock.unlock(); + } + } + + public void transmit(AMQCommand c) throws IOException { + _channelLock.lock(); + try { + ensureIsOpen(); + quiescingTransmit(c); + } finally { + _channelLock.unlock(); + } + } + + public void quiescingTransmit(Method m) throws IOException { + _channelLock.lock(); + try { + quiescingTransmit(new AMQCommand(m)); + } finally { + _channelLock.unlock(); + } + } + + public void quiescingTransmit(AMQCommand c) throws IOException { + _channelLock.lock(); + try { + if (c.getMethod().hasContent()) { + while (_blockContent) { + try { + _channelLockCondition.await(); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + + // This is to catch a situation when the thread wakes up during + // shutdown. Currently, no command that has content is allowed + // to send anything in a closing state. + ensureIsOpen(); + } + } + this._trafficListener.write(c); + c.transmit(this); + } finally { + _channelLock.unlock(); + } + } + + public AMQConnection getConnection() { + return _connection; + } + + public interface RpcContinuation { + void handleCommand(AMQCommand command); + /** @return true if the reply command can be handled for this request */ + boolean canHandleReply(AMQCommand command); + void handleShutdownSignal(ShutdownSignalException signal); + } + + public static abstract class BlockingRpcContinuation implements RpcContinuation { + final BlockingValueOrException _blocker = + new BlockingValueOrException<>(); + + protected final Method request; + + BlockingRpcContinuation() { + request = null; + } + + BlockingRpcContinuation(final Method request) { + this.request = request; + } + + @Override + public void handleCommand(AMQCommand command) { + _blocker.setValue(transformReply(command)); + } + + @Override + public void handleShutdownSignal(ShutdownSignalException signal) { + _blocker.setException(signal); + } + + public T getReply() throws ShutdownSignalException + { + return _blocker.uninterruptibleGetValue(); + } + + T getReply(int timeout) + throws ShutdownSignalException, TimeoutException + { + return _blocker.uninterruptibleGetValue(timeout); + } + + @Override + public boolean canHandleReply(AMQCommand command) { + return isResponseCompatibleWithRequest(request, command.getMethod()); + } + + public abstract T transformReply(AMQCommand command); + + static boolean isResponseCompatibleWithRequest(Method request, Method response) { + // make a best effort attempt to ensure the reply was intended for this rpc request + // Ideally each rpc request would tag an id on it that could be returned and referenced on its reply. + // But because that would be a very large undertaking to add passively this logic at least protects against ClassCastExceptions + if (request != null) { + if (request instanceof Basic.Qos) { + return response instanceof Basic.QosOk; + } else if (request instanceof Basic.Get) { + return response instanceof Basic.GetOk || response instanceof Basic.GetEmpty; + } else if (request instanceof Basic.Consume) { + if (!(response instanceof Basic.ConsumeOk)) + return false; + // can also check the consumer tags match here. handle case where request consumer tag is empty and server-generated. + final String consumerTag = ((Basic.Consume) request).getConsumerTag(); + return consumerTag == null || consumerTag.equals("") || consumerTag.equals(((Basic.ConsumeOk) response).getConsumerTag()); + } else if (request instanceof Basic.Cancel) { + if (!(response instanceof Basic.CancelOk)) + return false; + // can also check the consumer tags match here + return ((Basic.Cancel) request).getConsumerTag().equals(((Basic.CancelOk) response).getConsumerTag()); + } else if (request instanceof Basic.Recover) { + return response instanceof Basic.RecoverOk; + } else if (request instanceof Exchange.Declare) { + return response instanceof Exchange.DeclareOk; + } else if (request instanceof Exchange.Delete) { + return response instanceof Exchange.DeleteOk; + } else if (request instanceof Exchange.Bind) { + return response instanceof Exchange.BindOk; + } else if (request instanceof Exchange.Unbind) { + return response instanceof Exchange.UnbindOk; + } else if (request instanceof Queue.Declare) { + // we cannot check the queue name, as the server can strip some characters + // see QueueLifecycle test and https://github.com/rabbitmq/rabbitmq-server/issues/710 + return response instanceof Queue.DeclareOk; + } else if (request instanceof Queue.Delete) { + return response instanceof Queue.DeleteOk; + } else if (request instanceof Queue.Bind) { + return response instanceof Queue.BindOk; + } else if (request instanceof Queue.Unbind) { + return response instanceof Queue.UnbindOk; + } else if (request instanceof Queue.Purge) { + return response instanceof Queue.PurgeOk; + } else if (request instanceof Tx.Select) { + return response instanceof Tx.SelectOk; + } else if (request instanceof Tx.Commit) { + return response instanceof Tx.CommitOk; + } else if (request instanceof Tx.Rollback) { + return response instanceof Tx.RollbackOk; + } else if (request instanceof Confirm.Select) { + return response instanceof Confirm.SelectOk; + } + } + // for passivity default to true + return true; + } + } + + public static class SimpleBlockingRpcContinuation + extends BlockingRpcContinuation + { + + SimpleBlockingRpcContinuation() { + super(); + } + + SimpleBlockingRpcContinuation(final Method method) { + super(method); + } + + @Override + public AMQCommand transformReply(AMQCommand command) { + return command; + } + } + + protected ObservationCollector.ConnectionInfo connectionInfo() { + return this.connectionInfo; + } +} diff --git a/src/com/rabbitmq/client/impl/AMQCommand.java b/src/main/java/com/rabbitmq/client/impl/AMQCommand.java similarity index 63% rename from src/com/rabbitmq/client/impl/AMQCommand.java rename to src/main/java/com/rabbitmq/client/impl/AMQCommand.java index 88aecee324..fb19d6c263 100644 --- a/src/com/rabbitmq/client/impl/AMQCommand.java +++ b/src/main/java/com/rabbitmq/client/impl/AMQCommand.java @@ -1,33 +1,34 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Command; /** - * AMQ-specific implementation of {@link Command} which accumulates + * AMQP 0-9-1-specific implementation of {@link Command} which accumulates * method, header and body from a series of frames, unless these are * supplied at construction time. - *

Concurrency
+ *

Concurrency

* This class is thread-safe. */ public class AMQCommand implements Command { @@ -44,10 +45,15 @@ public class AMQCommand implements Command { /** The assembler for this command - synchronised on - contains all the state */ private final CommandAssembler assembler; + private final Lock assemblerLock = new ReentrantLock(); + + AMQCommand(int maxBodyLength) { + this(null, null, null, maxBodyLength); + } /** Construct a command ready to fill in by reading frames */ public AMQCommand() { - this(null, null, null); + this(null, null, null, Integer.MAX_VALUE); } /** @@ -55,7 +61,7 @@ public AMQCommand() { * @param method the wrapped method */ public AMQCommand(com.rabbitmq.client.Method method) { - this(method, null, null); + this(method, null, null, Integer.MAX_VALUE); } /** @@ -65,20 +71,35 @@ public AMQCommand(com.rabbitmq.client.Method method) { * @param body the message body data */ public AMQCommand(com.rabbitmq.client.Method method, AMQContentHeader contentHeader, byte[] body) { - this.assembler = new CommandAssembler((Method) method, contentHeader, body); + this.assembler = new CommandAssembler((Method) method, contentHeader, body, Integer.MAX_VALUE); + } + + /** + * Construct a command with a specified method, header and body. + * @param method the wrapped method + * @param contentHeader the wrapped content header + * @param body the message body data + * @param maxBodyLength the maximum size for an inbound message body + */ + public AMQCommand(com.rabbitmq.client.Method method, AMQContentHeader contentHeader, byte[] body, + int maxBodyLength) { + this.assembler = new CommandAssembler((Method) method, contentHeader, body, maxBodyLength); } /** Public API - {@inheritDoc} */ + @Override public Method getMethod() { return this.assembler.getMethod(); } /** Public API - {@inheritDoc} */ + @Override public AMQContentHeader getContentHeader() { return this.assembler.getContentHeader(); } /** Public API - {@inheritDoc} */ + @Override public byte[] getContentBody() { return this.assembler.getContentBody(); } @@ -97,18 +118,24 @@ public void transmit(AMQChannel channel) throws IOException { int channelNumber = channel.getChannelNumber(); AMQConnection connection = channel.getConnection(); - synchronized (assembler) { + assemblerLock.lock(); + try { Method m = this.assembler.getMethod(); - connection.writeFrame(m.toFrame(channelNumber)); if (m.hasContent()) { byte[] body = this.assembler.getContentBody(); - connection.writeFrame(this.assembler.getContentHeader() - .toFrame(channelNumber, body.length)); + Frame headerFrame = this.assembler.getContentHeader().toFrame(channelNumber, body.length); int frameMax = connection.getFrameMax(); - int bodyPayloadMax = (frameMax == 0) ? body.length : frameMax - - EMPTY_FRAME_SIZE; + boolean cappedFrameMax = frameMax > 0; + int bodyPayloadMax = cappedFrameMax ? frameMax - EMPTY_FRAME_SIZE : body.length; + + if (cappedFrameMax && headerFrame.size() > frameMax) { + String msg = String.format("Content headers exceeded max frame size: %d > %d", headerFrame.size(), frameMax); + throw new IllegalArgumentException(msg); + } + connection.writeFrame(m.toFrame(channelNumber)); + connection.writeFrame(headerFrame); for (int offset = 0; offset < body.length; offset += bodyPayloadMax) { int remaining = body.length - offset; @@ -119,7 +146,11 @@ public void transmit(AMQChannel channel) throws IOException { offset, fragmentLength); connection.writeFrame(frame); } + } else { + connection.writeFrame(m.toFrame(channelNumber)); } + } finally { + assemblerLock.unlock(); } connection.flush(); @@ -130,7 +161,8 @@ public void transmit(AMQChannel channel) throws IOException { } public String toString(boolean suppressBody){ - synchronized (assembler) { + assemblerLock.lock(); + try { return new StringBuilder() .append('{') .append(this.assembler.getMethod()) @@ -140,6 +172,8 @@ public String toString(boolean suppressBody){ .append(contentBodyStringBuilder( this.assembler.getContentBody(), suppressBody)) .append('}').toString(); + } finally { + assemblerLock.unlock(); } } @@ -171,11 +205,11 @@ private static void checkEmptyFrameSize() { try { f.writeTo(new DataOutputStream(s)); } catch (IOException ioe) { - throw new AssertionError("IOException while checking EMPTY_FRAME_SIZE"); + throw new IllegalStateException("IOException while checking EMPTY_FRAME_SIZE"); } int actualLength = s.toByteArray().length; if (EMPTY_FRAME_SIZE != actualLength) { - throw new AssertionError("Internal error: expected EMPTY_FRAME_SIZE(" + throw new IllegalStateException("Internal error: expected EMPTY_FRAME_SIZE(" + EMPTY_FRAME_SIZE + ") is not equal to computed value: " + actualLength); } diff --git a/src/com/rabbitmq/client/impl/AMQConnection.java b/src/main/java/com/rabbitmq/client/impl/AMQConnection.java similarity index 54% rename from src/com/rabbitmq/client/impl/AMQConnection.java rename to src/main/java/com/rabbitmq/client/impl/AMQConnection.java index 229876dc8f..1a91a3cd86 100644 --- a/src/com/rabbitmq/client/impl/AMQConnection.java +++ b/src/main/java/com/rabbitmq/client/impl/AMQConnection.java @@ -1,59 +1,42 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; +import com.rabbitmq.client.Method; +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.AMQChannel.BlockingRpcContinuation; +import com.rabbitmq.client.impl.recovery.RecoveryCanBeginListener; +import com.rabbitmq.client.observation.ObservationCollector; +import com.rabbitmq.utility.BlockingCell; +import com.rabbitmq.utility.Utility; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.EOFException; import java.io.IOException; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeoutException; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.AuthenticationFailureException; -import com.rabbitmq.client.BlockedListener; -import com.rabbitmq.client.ExceptionHandler; -import com.rabbitmq.client.Method; -import com.rabbitmq.client.AlreadyClosedException; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Command; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.LongString; -import com.rabbitmq.client.MissedHeartbeatException; -import com.rabbitmq.client.PossibleAuthenticationFailureException; -import com.rabbitmq.client.ProtocolVersionMismatchException; -import com.rabbitmq.client.SaslConfig; -import com.rabbitmq.client.SaslMechanism; -import com.rabbitmq.client.ShutdownSignalException; -import com.rabbitmq.client.impl.AMQChannel.BlockingRpcContinuation; -import com.rabbitmq.utility.BlockingCell; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; final class Copyright { - final static String COPYRIGHT="Copyright (C) 2007-2014 GoPivotal, Inc."; - final static String LICENSE="Licensed under the MPL. See http://www.rabbitmq.com/"; + final static String COPYRIGHT="Copyright (c) 2007-2025 Broadcom Inc. and/or its subsidiaries."; + final static String LICENSE="Licensed under the MPL. See https://www.rabbitmq.com/"; } /** @@ -63,11 +46,32 @@ final class Copyright { * for an example. */ public class AMQConnection extends ShutdownNotifierComponent implements Connection, NetworkConnection { - /** Timeout used while waiting for AMQP handshaking to complete (milliseconds) */ - public static final int HANDSHAKE_TIMEOUT = 10000; - private final ExecutorService executor; + + private static final int MAX_UNSIGNED_SHORT = 65535; + + private static final Logger LOGGER = LoggerFactory.getLogger(AMQConnection.class); + // we want socket write and channel shutdown timeouts to kick in after + // the heartbeat one, so we use a value of 105% of the effective heartbeat timeout + static final double CHANNEL_SHUTDOWN_TIMEOUT_MULTIPLIER = 1.05; + + private final ExecutorService consumerWorkServiceExecutor; + private final ScheduledExecutorService heartbeatExecutor; + private final ExecutorService shutdownExecutor; private Thread mainLoopThread; + private final AtomicBoolean ioLoopThreadSet = new AtomicBoolean(false); + private volatile Thread ioLoopThread; private ThreadFactory threadFactory = Executors.defaultThreadFactory(); + private String id; + + private final List recoveryCanBeginListeners = + Collections.synchronizedList(new ArrayList<>()); + + private final ErrorOnWriteListener errorOnWriteListener; + + private final int workPoolTimeout; + + private final AtomicBoolean finalShutdownStarted = new AtomicBoolean(false); + private volatile ObservationCollector.ConnectionInfo connectionInfo; /** * Retrieve a copy of the default table of client properties that @@ -77,15 +81,15 @@ public class AMQConnection extends ShutdownNotifierComponent implements Connecti * @return a map of client properties * @see Connection#getClientProperties */ - public static final Map defaultClientProperties() { - Map props = new HashMap(); + public static Map defaultClientProperties() { + Map props = new HashMap<>(); props.put("product", LongStringHelper.asLongString("RabbitMQ")); props.put("version", LongStringHelper.asLongString(ClientVersion.VERSION)); props.put("platform", LongStringHelper.asLongString("Java")); props.put("copyright", LongStringHelper.asLongString(Copyright.COPYRIGHT)); props.put("information", LongStringHelper.asLongString(Copyright.LICENSE)); - Map capabilities = new HashMap(); + Map capabilities = new HashMap<>(); capabilities.put("publisher_confirms", true); capabilities.put("exchange_exchange_bindings", true); capabilities.put("basic.nack", true); @@ -102,11 +106,7 @@ public static final Map defaultClientProperties() { new Version(AMQP.PROTOCOL.MAJOR, AMQP.PROTOCOL.MINOR); /** The special channel 0 (not managed by the _channelManager) */ - private final AMQChannel _channel0 = new AMQChannel(this, 0) { - @Override public boolean processAsync(Command c) throws IOException { - return getConnection().processControlCommand(c); - } - }; + private final AMQChannel _channel0; protected ConsumerWorkService _workService = null; @@ -122,7 +122,7 @@ public static final Map defaultClientProperties() { /** Object used for blocking main application thread when doing all the necessary * connection shutdown operations */ - private final BlockingCell _appContinuation = new BlockingCell(); + private final BlockingCell _appContinuation = new BlockingCell<>(); /** Flag indicating whether the client received Connection.Close message from the broker */ private volatile boolean _brokerInitiatedShutdown; @@ -139,10 +139,16 @@ public static final Map defaultClientProperties() { private final int requestedHeartbeat; private final int requestedChannelMax; private final int requestedFrameMax; + private final int handshakeTimeout; private final int shutdownTimeout; - private final String username; - private final String password; - private final Collection blockedListeners = new CopyOnWriteArrayList(); + private final CredentialsProvider credentialsProvider; + private final Collection blockedListeners = new CopyOnWriteArrayList<>(); + protected final MetricsCollector metricsCollector; + protected final ObservationCollector observationCollector; + private final int channelRpcTimeout; + private final boolean channelShouldCheckRpcResponseType; + private final TrafficListener trafficListener; + private final CredentialsRefreshService credentialsRefreshService; /* State modified after start - all volatile */ @@ -156,18 +162,19 @@ public static final Map defaultClientProperties() { private volatile ChannelManager _channelManager; /** Saved server properties field from connection.start */ private volatile Map _serverProperties; + private final int maxInboundMessageBodySize; /** - * Protected API - respond, in the driver thread, to a ShutdownSignal. + * Protected API - respond, in the main I/O loop thread, to a ShutdownSignal. * @param channel the channel to disconnect */ - public final void disconnectChannel(ChannelN channel) { + final void disconnectChannel(ChannelN channel) { ChannelManager cm = _channelManager; if (cm != null) cm.releaseChannelNumber(channel); } - private final void ensureIsOpen() + private void ensureIsOpen() throws AlreadyClosedException { if (!isOpen()) { @@ -176,19 +183,23 @@ private final void ensureIsOpen() } /** {@inheritDoc} */ + @Override public InetAddress getAddress() { return _frameHandler.getAddress(); } + @Override public InetAddress getLocalAddress() { return _frameHandler.getLocalAddress(); } /** {@inheritDoc} */ + @Override public int getPort() { return _frameHandler.getPort(); } + @Override public int getLocalPort() { return _frameHandler.getLocalPort(); } @@ -198,44 +209,80 @@ public FrameHandler getFrameHandler(){ } /** {@inheritDoc} */ + @Override public Map getServerProperties() { return _serverProperties; } + public AMQConnection(ConnectionParams params, FrameHandler frameHandler) { + this(params, frameHandler, new NoOpMetricsCollector(), ObservationCollector.NO_OP); + } + /** Construct a new connection * @param params parameters for it */ - public AMQConnection(ConnectionParams params, FrameHandler frameHandler) + public AMQConnection(ConnectionParams params, FrameHandler frameHandler, + MetricsCollector metricsCollector, ObservationCollector observationCollector) { checkPreconditions(); - this.username = params.getUsername(); - this.password = params.getPassword(); + this.credentialsProvider = params.getCredentialsProvider(); this._frameHandler = frameHandler; this._virtualHost = params.getVirtualHost(); this._exceptionHandler = params.getExceptionHandler(); - this._clientProperties = new HashMap(params.getClientProperties()); + this._clientProperties = new HashMap<>(params.getClientProperties()); this.requestedFrameMax = params.getRequestedFrameMax(); this.requestedChannelMax = params.getRequestedChannelMax(); this.requestedHeartbeat = params.getRequestedHeartbeat(); + this.handshakeTimeout = params.getHandshakeTimeout(); this.shutdownTimeout = params.getShutdownTimeout(); this.saslConfig = params.getSaslConfig(); - this.executor = params.getExecutor(); + this.consumerWorkServiceExecutor = params.getConsumerWorkServiceExecutor(); + this.heartbeatExecutor = params.getHeartbeatExecutor(); + this.shutdownExecutor = params.getShutdownExecutor(); this.threadFactory = params.getThreadFactory(); + if(params.getChannelRpcTimeout() < 0) { + throw new IllegalArgumentException("Continuation timeout on RPC calls cannot be less than 0"); + } + this.channelRpcTimeout = params.getChannelRpcTimeout(); + this.channelShouldCheckRpcResponseType = params.channelShouldCheckRpcResponseType(); + + this.trafficListener = params.getTrafficListener() == null ? TrafficListener.NO_OP : params.getTrafficListener(); + + this.credentialsRefreshService = params.getCredentialsRefreshService(); + + + this._channel0 = createChannel0(); this._channelManager = null; this._brokerInitiatedShutdown = false; this._inConnectionNegotiation = true; // we start out waiting for the first protocol response + + this.metricsCollector = metricsCollector; + this.observationCollector = observationCollector; + + this.errorOnWriteListener = params.getErrorOnWriteListener() != null ? params.getErrorOnWriteListener() : + (connection, exception) -> { throw exception; }; // we just propagate the exception for non-recoverable connections + this.workPoolTimeout = params.getWorkPoolTimeout(); + this.maxInboundMessageBodySize = params.getMaxInboundMessageBodySize(); + } + + AMQChannel createChannel0() { + return new AMQChannel(this, 0) { + @Override public boolean processAsync(Command c) throws IOException { + return getConnection().processControlCommand(c); + } + }; } private void initializeConsumerWorkService() { - this._workService = new ConsumerWorkService(executor, threadFactory, shutdownTimeout); + this._workService = new ConsumerWorkService(consumerWorkServiceExecutor, threadFactory, workPoolTimeout, shutdownTimeout); } private void initializeHeartbeatSender() { - this._heartbeatSender = new HeartbeatSender(_frameHandler, threadFactory); + this._heartbeatSender = new HeartbeatSender(_frameHandler, heartbeatExecutor, threadFactory); } /** @@ -255,8 +302,7 @@ private void initializeHeartbeatSender() { * garbage collected when the connection object is no longer referenced. */ public void start() - throws IOException - { + throws IOException, TimeoutException { initializeConsumerWorkService(); initializeHeartbeatSender(); this._running = true; @@ -274,68 +320,77 @@ public void start() try { // The following two lines are akin to AMQChannel's // transmit() method for this pseudo-RPC. - _frameHandler.setTimeout(HANDSHAKE_TIMEOUT); + _frameHandler.setTimeout(handshakeTimeout); _frameHandler.sendHeader(); } catch (IOException ioe) { _frameHandler.close(); throw ioe; } - // start the main loop going - MainLoop loop = new MainLoop(); - final String name = "AMQP Connection " + getHostAddress() + ":" + getPort(); - mainLoopThread = Environment.newThread(threadFactory, loop, name); - mainLoopThread.start(); - // after this point clear-up of MainLoop is triggered by closing the frameHandler. + this._frameHandler.initialize(this); - AMQP.Connection.Start connStart = null; + AMQP.Connection.Start connStart; AMQP.Connection.Tune connTune = null; try { connStart = - (AMQP.Connection.Start) connStartBlocker.getReply().getMethod(); + (AMQP.Connection.Start) connStartBlocker.getReply(handshakeTimeout/2).getMethod(); _serverProperties = Collections.unmodifiableMap(connStart.getServerProperties()); Version serverVersion = - new Version(connStart.getVersionMajor(), - connStart.getVersionMinor()); + new Version(connStart.getVersionMajor(), + connStart.getVersionMinor()); if (!Version.checkVersion(clientVersion, serverVersion)) { throw new ProtocolVersionMismatchException(clientVersion, - serverVersion); + serverVersion); } String[] mechanisms = connStart.getMechanisms().toString().split(" "); SaslMechanism sm = this.saslConfig.getSaslMechanism(mechanisms); if (sm == null) { throw new IOException("No compatible authentication mechanism found - " + - "server offered [" + connStart.getMechanisms() + "]"); + "server offered [" + connStart.getMechanisms() + "]"); + } + + String username = credentialsProvider.getUsername(); + String password = credentialsProvider.getPassword(); + + if (credentialsProvider.getTimeBeforeExpiration() != null) { + if (this.credentialsRefreshService == null) { + throw new IllegalStateException("Credentials can expire, a credentials refresh service should be set"); + } + if (this.credentialsRefreshService.isApproachingExpiration(credentialsProvider.getTimeBeforeExpiration())) { + credentialsProvider.refresh(); + username = credentialsProvider.getUsername(); + password = credentialsProvider.getPassword(); + } } LongString challenge = null; - LongString response = sm.handleChallenge(null, this.username, this.password); + LongString response = sm.handleChallenge(null, username, password); do { Method method = (challenge == null) - ? new AMQP.Connection.StartOk.Builder() - .clientProperties(_clientProperties) - .mechanism(sm.getName()) - .response(response) - .build() - : new AMQP.Connection.SecureOk.Builder().response(response).build(); + ? new AMQP.Connection.StartOk.Builder() + .clientProperties(_clientProperties) + .mechanism(sm.getName()) + .response(response) + .build() + : new AMQP.Connection.SecureOk.Builder().response(response).build(); try { - Method serverResponse = _channel0.rpc(method).getMethod(); + Method serverResponse = _channel0.rpc(method, handshakeTimeout/2).getMethod(); if (serverResponse instanceof AMQP.Connection.Tune) { connTune = (AMQP.Connection.Tune) serverResponse; } else { challenge = ((AMQP.Connection.Secure) serverResponse).getChallenge(); - response = sm.handleChallenge(challenge, this.username, this.password); + response = sm.handleChallenge(challenge, username, password); } } catch (ShutdownSignalException e) { Method shutdownMethod = e.getReason(); if (shutdownMethod instanceof AMQP.Connection.Close) { - AMQP.Connection.Close shutdownClose = (AMQP.Connection.Close) shutdownMethod; + AMQP.Connection.Close shutdownClose = (AMQP.Connection.Close) shutdownMethod; if (shutdownClose.getReplyCode() == AMQP.ACCESS_REFUSED) { throw new AuthenticationFailureException(shutdownClose.getReplyText()); } @@ -343,35 +398,49 @@ public void start() throw new PossibleAuthenticationFailureException(e); } } while (connTune == null); + } catch (TimeoutException | IOException te) { + _frameHandler.close(); + throw te; } catch (ShutdownSignalException sse) { _frameHandler.close(); throw AMQChannel.wrap(sse); - } catch(IOException ioe) { - _frameHandler.close(); - throw ioe; } try { - int channelMax = + int negotiatedChannelMax = negotiateChannelMax(this.requestedChannelMax, connTune.getChannelMax()); - _channelManager = instantiateChannelManager(channelMax, threadFactory); + + if (!checkUnsignedShort(negotiatedChannelMax)) { + throw new IllegalArgumentException("Negotiated channel max must be between 0 and " + MAX_UNSIGNED_SHORT + ": " + negotiatedChannelMax); + } + + _channelManager = instantiateChannelManager(negotiatedChannelMax, threadFactory); int frameMax = negotiatedMaxValue(this.requestedFrameMax, connTune.getFrameMax()); this._frameMax = frameMax; - int heartbeat = + int negotiatedHeartbeat = negotiatedMaxValue(this.requestedHeartbeat, connTune.getHeartbeat()); - setHeartbeat(heartbeat); + if (!checkUnsignedShort(negotiatedHeartbeat)) { + throw new IllegalArgumentException("Negotiated heartbeat must be between 0 and " + MAX_UNSIGNED_SHORT + ": " + negotiatedHeartbeat); + } + + setHeartbeat(negotiatedHeartbeat); + + this.connectionInfo = new DefaultConnectionInfo( + getAddress(), + getPort() + ); _channel0.transmit(new AMQP.Connection.TuneOk.Builder() - .channelMax(channelMax) + .channelMax(negotiatedChannelMax) .frameMax(frameMax) - .heartbeat(heartbeat) + .heartbeat(negotiatedHeartbeat) .build()); _channel0.exnWrappingRpc(new AMQP.Connection.Open.Builder() .virtualHost(_virtualHost) @@ -386,14 +455,59 @@ public void start() throw AMQChannel.wrap(sse); } + if (this.credentialsProvider.getTimeBeforeExpiration() != null) { + String registrationId = this.credentialsRefreshService.register(credentialsProvider, () -> { + // return false if connection is closed, so refresh service can get rid of this registration + if (!isOpen()) { + return false; + } + if (this._inConnectionNegotiation) { + // this should not happen + return true; + } + String refreshedPassword = credentialsProvider.getPassword(); + + UpdateSecretExtension.UpdateSecret updateSecret = new UpdateSecretExtension.UpdateSecret( + LongStringHelper.asLongString(refreshedPassword), "Refresh scheduled by client" + ); + try { + _channel0.rpc(updateSecret); + } catch (ShutdownSignalException e) { + LOGGER.warn("Error while trying to update secret: {}. Connection has been closed.", e.getMessage()); + return false; + } + return true; + }); + + addShutdownListener(sse -> this.credentialsRefreshService.unregister(this.credentialsProvider, registrationId)); + } + // We can now respond to errors having finished tailoring the connection this._inConnectionNegotiation = false; - - return; } protected ChannelManager instantiateChannelManager(int channelMax, ThreadFactory threadFactory) { - return new ChannelManager(this._workService, channelMax, threadFactory); + ChannelManager result = new ChannelManager( + this._workService, channelMax, threadFactory, + this.metricsCollector, this.observationCollector); + configureChannelManager(result); + return result; + } + + protected void configureChannelManager(ChannelManager channelManager) { + channelManager.setShutdownExecutor(this.shutdownExecutor); + channelManager.setChannelShutdownTimeout((int) ((requestedHeartbeat * CHANNEL_SHUTDOWN_TIMEOUT_MULTIPLIER) * 1000)); + } + + /** + * Package private API, allows for easier testing. + */ + public void startMainLoop() { + MainLoop loop = new MainLoop(); + final String name = "AMQP Connection " + getHostAddress() + ":" + getPort(); + mainLoopThread = Environment.newThread(threadFactory, loop, name); + ioLoopThread(mainLoopThread); + mainLoopThread.start(); } /** @@ -406,11 +520,12 @@ protected int negotiateChannelMax(int requestedChannelMax, int serverMax) { /** * Private API - check required preconditions and protocol invariants */ - private static final void checkPreconditions() { + private static void checkPreconditions() { AMQCommand.checkPreconditions(); } /** {@inheritDoc} */ + @Override public int getChannelMax() { ChannelManager cm = _channelManager; if (cm == null) return 0; @@ -418,11 +533,13 @@ public int getChannelMax() { } /** {@inheritDoc} */ + @Override public int getFrameMax() { return _frameMax; } /** {@inheritDoc} */ + @Override public int getHeartbeat() { return _heartbeat; } @@ -449,7 +566,7 @@ public void setHeartbeat(int heartbeat) { * Makes it possible to override thread factory that is used * to instantiate connection network I/O loop. Only necessary * in the environments with restricted - * @param threadFactory + * @param threadFactory thread factory to use */ public void setThreadFactory(ThreadFactory threadFactory) { this.threadFactory = threadFactory; @@ -462,13 +579,20 @@ public ThreadFactory getThreadFactory() { return threadFactory; } + @Override public Map getClientProperties() { - return new HashMap(_clientProperties); + return new HashMap<>(_clientProperties); + } + + @Override + public String getClientProvidedName() { + return (String) _clientProperties.get("connection_name"); } /** * Protected API - retrieve the current ExceptionHandler */ + @Override public ExceptionHandler getExceptionHandler() { return _exceptionHandler; } @@ -476,7 +600,7 @@ public ExceptionHandler getExceptionHandler() { /** Public API * - * @return true if this work service instance uses its own executor (as opposed to a shared one) + * @return true if this work service instance uses its own consumerWorkServiceExecutor (as opposed to a shared one) */ public boolean willShutDownConsumerExecutor() { return this._workService.usesPrivateExecutor(); @@ -484,25 +608,35 @@ public boolean willShutDownConsumerExecutor() { /** Public API - {@inheritDoc} */ + @Override public Channel createChannel(int channelNumber) throws IOException { ensureIsOpen(); ChannelManager cm = _channelManager; if (cm == null) return null; - return cm.createChannel(this, channelNumber); + Channel channel = cm.createChannel(this, channelNumber); + if (channel != null) { + metricsCollector.newChannel(channel); + } + return channel; } /** Public API - {@inheritDoc} */ + @Override public Channel createChannel() throws IOException { ensureIsOpen(); ChannelManager cm = _channelManager; if (cm == null) return null; - return cm.createChannel(this); + Channel channel = cm.createChannel(this); + if (channel != null) { + metricsCollector.newChannel(channel); + } + return channel; } /** * Public API - sends a frame directly to the broker. */ - public void writeFrame(Frame f) throws IOException { + void writeFrame(Frame f) throws IOException { _frameHandler.writeFrame(f); _heartbeatSender.signalActivity(); } @@ -511,15 +645,23 @@ public void writeFrame(Frame f) throws IOException { * Public API - flush the output buffers */ public void flush() throws IOException { - _frameHandler.flush(); + try { + _frameHandler.flush(); + } catch (IOException ioe) { + this.errorOnWriteListener.handle(this, ioe); + } } - private static final int negotiatedMaxValue(int clientValue, int serverValue) { + private static int negotiatedMaxValue(int clientValue, int serverValue) { return (clientValue == 0 || serverValue == 0) ? Math.max(clientValue, serverValue) : Math.min(clientValue, serverValue); } + private static boolean checkUnsignedShort(int value) { + return value >= 0 && value <= MAX_UNSIGNED_SHORT; + } + private class MainLoop implements Runnable { /** @@ -528,54 +670,176 @@ private class MainLoop implements Runnable { * Continues running until the "running" flag is set false by * shutdown(). */ + @Override public void run() { + boolean shouldDoFinalShutdown = true; try { while (_running) { Frame frame = _frameHandler.readFrame(); + readFrame(frame); + } + } catch (Throwable ex) { + if (ex instanceof InterruptedException) { + // loop has been interrupted during shutdown, + // no need to do it again + shouldDoFinalShutdown = false; + } else { + handleFailure(ex); + } + } finally { + if (shouldDoFinalShutdown) { + doFinalShutdown(); + } + } + } + } - if (frame != null) { - _missedHeartbeats = 0; - if (frame.type == AMQP.FRAME_HEARTBEAT) { - // Ignore it: we've already just reset the heartbeat counter. - } else { - if (frame.channel == 0) { // the special channel - _channel0.handleFrame(frame); - } else { - if (isOpen()) { - // If we're still _running, but not isOpen(), then we - // must be quiescing, which means any inbound frames - // for non-zero channels (and any inbound commands on - // channel zero that aren't Connection.CloseOk) must - // be discarded. - ChannelManager cm = _channelManager; - if (cm != null) { - cm.getChannel(frame.channel).handleFrame(frame); - } - } + /** private API */ + public boolean handleReadFrame(Frame frame) { + if(_running) { + try { + readFrame(frame); + return true; + } catch (WorkPoolFullException e) { + // work pool is full, we propagate this one. + throw e; + } catch (Throwable ex) { + try { + handleFailure(ex); + } finally { + doFinalShutdown(); + } + } + } + return false; + } + + public boolean isRunning() { + return _running; + } + + public boolean hasBrokerInitiatedShutdown() { + return _brokerInitiatedShutdown; + } + + private void readFrame(Frame frame) throws IOException { + if (frame != null) { + _missedHeartbeats = 0; + if (frame.getType() == AMQP.FRAME_HEARTBEAT) { + // Ignore it: we've already just reset the heartbeat counter. + } else { + if (frame.getChannel() == 0) { // the special channel + _channel0.handleFrame(frame); + } else { + if (isOpen()) { + // If we're still _running, but not isOpen(), then we + // must be quiescing, which means any inbound frames + // for non-zero channels (and any inbound commands on + // channel zero that aren't Connection.CloseOk) must + // be discarded. + ChannelManager cm = _channelManager; + if (cm != null) { + ChannelN channel; + try { + channel = cm.getChannel(frame.getChannel()); + } catch(UnknownChannelException e) { + // this can happen if channel has been closed, + // but there was e.g. an in-flight delivery. + // just ignoring the frame to avoid closing the whole connection + LOGGER.info("Received a frame on an unknown channel, ignoring it"); + return; } + channel.handleFrame(frame); } - } else { - // Socket timeout waiting for a frame. - // Maybe missed heartbeat. - handleSocketTimeout(); } } - } catch (EOFException ex) { - if (!_brokerInitiatedShutdown) - shutdown(null, false, ex, true); - } catch (Throwable ex) { - _exceptionHandler.handleUnexpectedConnectionDriverException(AMQConnection.this, - ex); + } + } else { + // Socket timeout waiting for a frame. + // Maybe missed heartbeat. + handleSocketTimeout(); + } + } + + /** private API */ + public void handleHeartbeatFailure() { + Exception ex = new MissedHeartbeatException("Detected missed server heartbeats, heartbeat interval: " + + _heartbeat + " seconds, RabbitMQ node hostname: " + this.getHostAddress()); + try { + _exceptionHandler.handleUnexpectedConnectionDriverException(this, ex); + shutdown(null, false, ex, true); + } finally { + doFinalShutdown(); + } + } + + /** private API */ + public void handleIoError(Throwable ex) { + try { + handleFailure(ex); + } finally { + doFinalShutdown(); + } + } + + private void handleFailure(Throwable ex) { + if(ex instanceof EOFException) { + if (!_brokerInitiatedShutdown) shutdown(null, false, ex, true); - } finally { - // Finally, shut down our underlying data connection. - _frameHandler.close(); - _appContinuation.set(null); - notifyListeners(); + } else { + _exceptionHandler.handleUnexpectedConnectionDriverException(AMQConnection.this, + ex); + shutdown(null, false, ex, true); + } + } + + /** private API */ + public void doFinalShutdown() { + if (finalShutdownStarted.compareAndSet(false, true)) { + _frameHandler.close(); + _appContinuation.set(null); + closeMainLoopThreadIfNecessary(); + notifyListeners(); + // assuming that shutdown listeners do not do anything + // asynchronously, e.g. start new threads, this effectively + // guarantees that we only begin recovery when all shutdown + // listeners have executed + notifyRecoveryCanBeginListeners(); + } + } + + private void closeMainLoopThreadIfNecessary() { + if (mainLoopReadThreadNotNull() && notInMainLoopThread()) { + if (this.mainLoopThread.isAlive()) { + this.mainLoopThread.interrupt(); } } } + private boolean notInMainLoopThread() { + return Thread.currentThread() != this.mainLoopThread; + } + + private boolean mainLoopReadThreadNotNull() { + return this.mainLoopThread != null; + } + + private void notifyRecoveryCanBeginListeners() { + ShutdownSignalException sse = this.getCloseReason(); + for(RecoveryCanBeginListener fn : Utility.copy(this.recoveryCanBeginListeners)) { + fn.recoveryCanBegin(sse); + } + } + + public void addRecoveryCanBeginListener(RecoveryCanBeginListener fn) { + this.recoveryCanBeginListeners.add(fn); + } + + @SuppressWarnings("unused") + public void removeRecoveryCanBeginListener(RecoveryCanBeginListener fn) { + this.recoveryCanBeginListeners.remove(fn); + } + /** * Called when a frame-read operation times out * @throws MissedHeartbeatException if heart-beats have been missed @@ -595,7 +859,7 @@ private void handleSocketTimeout() throws SocketTimeoutException { // of the heartbeat setting in setHeartbeat above. if (++_missedHeartbeats > (2 * 4)) { throw new MissedHeartbeatException("Heartbeat missing with heartbeat = " + - _heartbeat + " seconds"); + _heartbeat + " seconds, for " + this.getHostAddress()); } } @@ -644,7 +908,7 @@ public boolean processControlCommand(Command c) throws IOException // Already shutting down, so just send back a CloseOk. try { _channel0.quiescingTransmit(new AMQP.Connection.CloseOk.Builder().build()); - } catch (IOException _e) { } // ignore + } catch (IOException ignored) { } // ignore return true; } else if (method instanceof AMQP.Connection.CloseOk) { // It's our final "RPC". Time to shut down. @@ -659,33 +923,49 @@ public boolean processControlCommand(Command c) throws IOException } } - @SuppressWarnings("unused") - public void handleConnectionClose(Command closeCommand) { + private void handleConnectionClose(Command closeCommand) { ShutdownSignalException sse = shutdown(closeCommand.getMethod(), false, null, _inConnectionNegotiation); try { _channel0.quiescingTransmit(new AMQP.Connection.CloseOk.Builder().build()); - } catch (IOException _e) { } // ignore + } catch (IOException ignored) { } // ignore _brokerInitiatedShutdown = true; SocketCloseWait scw = new SocketCloseWait(sse); - final String name = "AMQP Connection Closing Monitor " + + + // if shutdown executor is configured, use it. Otherwise + // execute socket close monitor the old fashioned way. + // see rabbitmq/rabbitmq-java-client#91 + if(shutdownExecutor != null) { + shutdownExecutor.execute(scw); + } else { + final String name = "RabbitMQ connection shutdown monitor " + getHostAddress() + ":" + getPort(); - Thread waiter = Environment.newThread(threadFactory, scw, name); - waiter.start(); + Thread waiter = Environment.newThread(threadFactory, scw, name); + waiter.start(); + } } private class SocketCloseWait implements Runnable { + // same as ConnectionFactory.DEFAULT_SHUTDOWN_TIMEOUT + private long SOCKET_CLOSE_TIMEOUT = 10000; + private final ShutdownSignalException cause; - public SocketCloseWait(ShutdownSignalException sse) { + SocketCloseWait(ShutdownSignalException sse) { cause = sse; } + @Override public void run() { try { - _appContinuation.uninterruptibleGet(); + // TODO: use a sensible timeout here + _appContinuation.get(SOCKET_CLOSE_TIMEOUT); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (TimeoutException ignored) { + // this releases the thread } finally { _running = false; - _channel0.notifyOutstandingRpc(cause); + _channel0.notifyOutstandingRpc(cause); } } } @@ -728,7 +1008,6 @@ private ShutdownSignalException startShutdown(Method reason, _heartbeatSender.shutdown(); _channel0.processShutdownSignal(sse, !initiatedByApplication, notifyRpc); - return sse; } @@ -738,6 +1017,7 @@ private void finishShutdown(ShutdownSignalException sse) { } /** Public API - {@inheritDoc} */ + @Override public void close() throws IOException { @@ -745,6 +1025,7 @@ public void close() } /** Public API - {@inheritDoc} */ + @Override public void close(int timeout) throws IOException { @@ -752,6 +1033,7 @@ public void close(int timeout) } /** Public API - {@inheritDoc} */ + @Override public void close(int closeCode, String closeMessage) throws IOException { @@ -759,6 +1041,7 @@ public void close(int closeCode, String closeMessage) } /** Public API - {@inheritDoc} */ + @Override public void close(int closeCode, String closeMessage, int timeout) throws IOException { @@ -766,30 +1049,33 @@ public void close(int closeCode, String closeMessage, int timeout) } /** Public API - {@inheritDoc} */ + @Override public void abort() { abort(-1); } /** Public API - {@inheritDoc} */ + @Override public void abort(int closeCode, String closeMessage) { abort(closeCode, closeMessage, -1); } /** Public API - {@inheritDoc} */ + @Override public void abort(int timeout) { abort(AMQP.REPLY_SUCCESS, "OK", timeout); } /** Public API - {@inheritDoc} */ - @SuppressWarnings("unused") + @Override public void abort(int closeCode, String closeMessage, int timeout) { try { close(closeCode, closeMessage, true, null, timeout, true); - } catch (IOException _e) { } // ignore + } catch (IOException ignored) { } // ignore } /** @@ -821,7 +1107,7 @@ public void close(int closeCode, boolean abort) throws IOException { - boolean sync = !(Thread.currentThread() == mainLoopThread); + boolean sync = !(Thread.currentThread() == ioLoopThread); try { AMQP.Connection.Close reason = @@ -850,34 +1136,113 @@ public AMQCommand transformReply(AMQCommand command) { sse.initCause(cause); throw sse; } - } catch (ShutdownSignalException sse) { + } catch (ShutdownSignalException | IOException sse) { if (!abort) throw sse; - } catch (IOException ioe) { - if (!abort) - throw ioe; } finally { if(sync) _frameHandler.close(); } } @Override public String toString() { - return "amqp://" + this.username + "@" + getHostAddress() + ":" + getPort() + _virtualHost; + final String virtualHost = "/".equals(_virtualHost) ? _virtualHost : "/" + _virtualHost; + return "amqp://" + this.credentialsProvider.getUsername() + "@" + getHostAddress() + ":" + getPort() + virtualHost; } private String getHostAddress() { return getAddress() == null ? null : getAddress().getHostAddress(); } + @Override public void addBlockedListener(BlockedListener listener) { blockedListeners.add(listener); } + @Override + public BlockedListener addBlockedListener(BlockedCallback blockedCallback, UnblockedCallback unblockedCallback) { + BlockedListener blockedListener = new BlockedListener() { + + @Override + public void handleBlocked(String reason) throws IOException { + blockedCallback.handle(reason); + } + + @Override + public void handleUnblocked() throws IOException { + unblockedCallback.handle(); + } + }; + this.addBlockedListener(blockedListener); + return blockedListener; + } + + @Override public boolean removeBlockedListener(BlockedListener listener) { return blockedListeners.remove(listener); } + @Override public void clearBlockedListeners() { blockedListeners.clear(); } + + /** Public API - {@inheritDoc} */ + @Override + public String getId() { + return id; + } + + /** Public API - {@inheritDoc} */ + @Override + public void setId(String id) { + this.id = id; + } + + public void ioLoopThread(Thread thread) { + if (this.ioLoopThreadSet.compareAndSet(false, true)) { + this.ioLoopThread = thread; + } + } + + public int getChannelRpcTimeout() { + return channelRpcTimeout; + } + + public boolean willCheckRpcResponseType() { + return channelShouldCheckRpcResponseType; + } + + public TrafficListener getTrafficListener() { + return trafficListener; + } + + int getMaxInboundMessageBodySize() { + return maxInboundMessageBodySize; + } + + private static class DefaultConnectionInfo implements ObservationCollector.ConnectionInfo { + + private final String peerAddress; + private final int peerPort; + + private DefaultConnectionInfo(InetAddress address, int peerPort) { + this.peerAddress = address == null ? "" : (address.getHostAddress() == null ? "" : address.getHostAddress()); + this.peerPort = peerPort; + } + + @Override + public String getPeerAddress() { + return peerAddress; + } + + @Override + public int getPeerPort() { + return this.peerPort; + } + + } + + ObservationCollector.ConnectionInfo connectionInfo() { + return this.connectionInfo; + } } diff --git a/src/com/rabbitmq/client/impl/AMQContentHeader.java b/src/main/java/com/rabbitmq/client/impl/AMQContentHeader.java similarity index 72% rename from src/com/rabbitmq/client/impl/AMQContentHeader.java rename to src/main/java/com/rabbitmq/client/impl/AMQContentHeader.java index 8a16bb7899..c106424daa 100644 --- a/src/com/rabbitmq/client/impl/AMQContentHeader.java +++ b/src/main/java/com/rabbitmq/client/impl/AMQContentHeader.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -57,6 +56,7 @@ private void writeTo(DataOutputStream out, long bodySize) throws IOException { public abstract void writePropertiesTo(ContentHeaderPropertyWriter writer) throws IOException; /** Public API - {@inheritDoc} */ + @Override public void appendPropertyDebugStringTo(StringBuilder acc) { acc.append("(?)"); } @@ -79,6 +79,7 @@ public Frame toFrame(int channelNumber, long bodySize) throws IOException { return frame; } + @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } diff --git a/src/main/java/com/rabbitmq/client/impl/AbstractFrameHandlerFactory.java b/src/main/java/com/rabbitmq/client/impl/AbstractFrameHandlerFactory.java new file mode 100644 index 0000000000..576d4490cf --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/AbstractFrameHandlerFactory.java @@ -0,0 +1,37 @@ +// Copyright (c) 2016-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.SocketConfigurator; + +/** + * + */ +public abstract class AbstractFrameHandlerFactory implements FrameHandlerFactory { + + protected final int connectionTimeout; + protected final SocketConfigurator configurator; + protected final boolean ssl; + protected final int maxInboundMessageBodySize; + + protected AbstractFrameHandlerFactory(int connectionTimeout, SocketConfigurator configurator, + boolean ssl, int maxInboundMessageBodySize) { + this.connectionTimeout = connectionTimeout; + this.configurator = configurator; + this.ssl = ssl; + this.maxInboundMessageBodySize = maxInboundMessageBodySize; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/AbstractMetricsCollector.java b/src/main/java/com/rabbitmq/client/impl/AbstractMetricsCollector.java new file mode 100644 index 0000000000..8f7c9b3320 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/AbstractMetricsCollector.java @@ -0,0 +1,467 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; + +/** + * Base class for {@link MetricsCollector}. + * Implements tricky logic such as keeping track of acknowledged and + * rejected messages. Sub-classes just need to implement + * the logic to increment their metrics. + * Note transactions are not supported (see {@link MetricsCollector}. + * + * @see MetricsCollector + */ +public abstract class AbstractMetricsCollector implements MetricsCollector { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMetricsCollector.class); + + private final ConcurrentMap connectionState = new ConcurrentHashMap<>(); + + private final Runnable markAcknowledgedMessageAction = () -> markAcknowledgedMessage(); + + private final Function markRejectedMessageAction; + + private final Runnable markMessagePublishAcknowledgedAction = () -> markMessagePublishAcknowledged(); + + private final Runnable markMessagePublishNotAcknowledgedAction = () -> markMessagePublishNotAcknowledged(); + + private static final Function> GET_UNACKED_DTAGS = channelState -> channelState.unackedMessageDeliveryTags; + + private static final Function> GET_UNCONFIRMED_DTAGS = channelState -> channelState.unconfirmedMessageDeliveryTags; + + public AbstractMetricsCollector() { + Runnable rejectRequeue = () -> markRejectedMessage(true); + Runnable rejectNoRequeue = () -> markRejectedMessage(false); + this.markRejectedMessageAction = requeue -> requeue ? rejectRequeue : rejectNoRequeue; + } + + @Override + public void newConnection(final Connection connection) { + try { + if(connection.getId() == null) { + connection.setId(UUID.randomUUID().toString()); + } + incrementConnectionCount(connection); + connectionState.put(connection.getId(), new ConnectionState(connection)); + connection.addShutdownListener(cause -> closeConnection(connection)); + } catch(Exception e) { + LOGGER.info("Error while computing metrics in newConnection: " + e.getMessage()); + } + } + + @Override + public void closeConnection(Connection connection) { + try { + ConnectionState removed = connectionState.remove(connection.getId()); + if(removed != null) { + decrementConnectionCount(connection); + } + } catch(Exception e) { + LOGGER.info("Error while computing metrics in closeConnection: " + e.getMessage()); + } + } + + @Override + public void newChannel(final Channel channel) { + if (channel != null) { + try { + incrementChannelCount(channel); + channel.addShutdownListener(cause -> closeChannel(channel)); + connectionState(channel.getConnection()).channelState.put(channel.getChannelNumber(), new ChannelState(channel)); + } catch(Exception e) { + LOGGER.info("Error while computing metrics in newChannel: " + e.getMessage()); + } + } + } + + @Override + public void closeChannel(Channel channel) { + try { + ChannelState removed = connectionState(channel.getConnection()).channelState.remove(channel.getChannelNumber()); + if(removed != null) { + decrementChannelCount(channel); + } + } catch(Exception e) { + LOGGER.info("Error while computing metrics in closeChannel: " + e.getMessage()); + } + } + + @Override + public void basicPublish(Channel channel, long deliveryTag) { + try { + if (deliveryTag != 0) { + ChannelState channelState = channelState(channel); + if (channelState == null) { + return; + } + channelState.lock.lock(); + try { + channelState.unconfirmedMessageDeliveryTags.add(deliveryTag); + } finally { + channelState.lock.unlock(); + } + } + markPublishedMessage(); + } catch(Exception e) { + LOGGER.info("Error while computing metrics in basicPublish: " + e.getMessage()); + } + } + + @Override + public void basicPublishFailure(Channel channel, Throwable cause) { + try { + markMessagePublishFailed(); + } catch(Exception e) { + LOGGER.info("Error while computing metrics in basicPublishFailure: " + e.getMessage()); + } + } + + @Override + public void basicPublishAck(Channel channel, long deliveryTag, boolean multiple) { + try { + updateChannelStateAfterAckReject(channel, deliveryTag, multiple, GET_UNCONFIRMED_DTAGS, markMessagePublishAcknowledgedAction); + } catch (Exception e) { + LOGGER.info("Error while computing metrics in basicPublishAck: " + e.getMessage()); + } + } + + @Override + public void basicPublishNack(Channel channel, long deliveryTag, boolean multiple) { + try { + updateChannelStateAfterAckReject(channel, deliveryTag, multiple, GET_UNCONFIRMED_DTAGS, markMessagePublishNotAcknowledgedAction); + } catch (Exception e) { + LOGGER.info("Error while computing metrics in basicPublishNack: " + e.getMessage()); + } + } + + @Override + public void basicPublishUnrouted(Channel channel) { + try { + markPublishedMessageUnrouted(); + } catch(Exception e) { + LOGGER.info("Error while computing metrics in markPublishedMessageUnrouted: " + e.getMessage()); + } + } + + @Override + public void basicConsume(Channel channel, String consumerTag, boolean autoAck) { + try { + if(!autoAck) { + ChannelState channelState = channelState(channel); + if (channelState == null) { + return; + } + channelState.lock.lock(); + try { + channelState.consumersWithManualAck.add(consumerTag); + } finally { + channelState.lock.unlock(); + } + } + } catch(Exception e) { + LOGGER.info("Error while computing metrics in basicConsume: " + e.getMessage()); + } + } + + @Override + public void basicCancel(Channel channel, String consumerTag) { + try { + ChannelState channelState = channelState(channel); + if (channelState == null) { + return; + } + channelState.lock.lock(); + try { + channelState.consumersWithManualAck.remove(consumerTag); + } finally { + channelState.lock.unlock(); + } + } catch(Exception e) { + LOGGER.info("Error while computing metrics in basicCancel: " + e.getMessage()); + } + } + + @Override + public void consumedMessage(Channel channel, long deliveryTag, boolean autoAck) { + try { + markConsumedMessage(); + if(!autoAck) { + ChannelState channelState = channelState(channel); + if (channelState == null) { + return; + } + channelState.lock.lock(); + try { + channelState.unackedMessageDeliveryTags.add(deliveryTag); + } finally { + channelState.lock.unlock(); + } + } + } catch(Exception e) { + LOGGER.info("Error while computing metrics in consumedMessage: " + e.getMessage()); + } + } + + @Override + public void consumedMessage(Channel channel, long deliveryTag, String consumerTag) { + try { + markConsumedMessage(); + ChannelState channelState = channelState(channel); + if (channelState == null) { + return; + } + channelState.lock.lock(); + try { + if(channelState.consumersWithManualAck.contains(consumerTag)) { + channelState.unackedMessageDeliveryTags.add(deliveryTag); + } + } finally { + channelState.lock.unlock(); + } + } catch(Exception e) { + LOGGER.info("Error while computing metrics in consumedMessage: " + e.getMessage()); + } + } + + @Override + public void basicAck(Channel channel, long deliveryTag, boolean multiple) { + try { + updateChannelStateAfterAckReject(channel, deliveryTag, multiple, GET_UNACKED_DTAGS, markAcknowledgedMessageAction); + } catch(Exception e) { + LOGGER.info("Error while computing metrics in basicAck: " + e.getMessage()); + } + } + + @Override + public void basicNack(Channel channel, long deliveryTag) { + // replaced by #basicNack(Channel, long, boolean) + } + + @Override + public void basicNack(Channel channel, long deliveryTag, boolean requeue) { + try { + updateChannelStateAfterAckReject(channel, deliveryTag, true, GET_UNACKED_DTAGS, markRejectedMessageAction.apply(requeue)); + } catch(Exception e) { + LOGGER.info("Error while computing metrics in basicNack: " + e.getMessage()); + } + } + + @Override + public void basicReject(Channel channel, long deliveryTag) { + // replaced by #basicReject(Channel, long, boolean) + } + + @Override + public void basicReject(Channel channel, long deliveryTag, boolean requeue) { + try { + updateChannelStateAfterAckReject(channel, deliveryTag, false, GET_UNACKED_DTAGS, markRejectedMessageAction.apply(requeue)); + } catch(Exception e) { + LOGGER.info("Error while computing metrics in basicReject: " + e.getMessage()); + } + } + + private void updateChannelStateAfterAckReject(Channel channel, long deliveryTag, boolean multiple, + Function> dtags, Runnable action) { + ChannelState channelState = channelState(channel); + channelState.lock.lock(); + try { + if(multiple) { + Iterator iterator = dtags.apply(channelState).iterator(); + while(iterator.hasNext()) { + long messageDeliveryTag = iterator.next(); + if(messageDeliveryTag <= deliveryTag) { + iterator.remove(); + action.run(); + } + } + } else { + dtags.apply(channelState).remove(deliveryTag); + // we always run the action, whether the set contains the delivery tag + // the collection may not contain the tag yet, if the ack/confirm arrives very fast + // so checking the result of Collection#remove may not be exact. + action.run(); + } + } finally { + channelState.lock.unlock(); + } + } + + private ConnectionState connectionState(Connection connection) { + return connectionState.get(connection.getId()); + } + + private ChannelState channelState(Channel channel) { + return connectionState(channel.getConnection()).channelState.get(channel.getChannelNumber()); + } + + /** + * Clean inner state for close connections and channels. + * Inner state is automatically cleaned on connection + * and channel closing. + * Thus, this method is provided as a safety net, to be externally + * called periodically if closing of resources wouldn't work + * properly for some corner cases. + */ + public void cleanStaleState() { + try { + Iterator> connectionStateIterator = connectionState.entrySet().iterator(); + while(connectionStateIterator.hasNext()) { + Map.Entry connectionEntry = connectionStateIterator.next(); + Connection connection = connectionEntry.getValue().connection; + if(connection.isOpen()) { + Iterator> channelStateIterator = connectionEntry.getValue().channelState.entrySet().iterator(); + while(channelStateIterator.hasNext()) { + Map.Entry channelStateEntry = channelStateIterator.next(); + Channel channel = channelStateEntry.getValue().channel; + if(!channel.isOpen()) { + channelStateIterator.remove(); + decrementChannelCount(channel); + LOGGER.info("Ripped off state of channel {} of connection {}. This is abnormal, please report.", + channel.getChannelNumber(), connection.getId()); + } + } + } else { + connectionStateIterator.remove(); + decrementConnectionCount(connection); + for(int i = 0; i < connectionEntry.getValue().channelState.size(); i++) { + decrementChannelCount(null); + } + LOGGER.info("Ripped off state of connection {}. This is abnormal, please report.", + connection.getId()); + } + } + } catch(Exception e) { + LOGGER.info("Error during periodic clean of metricsCollector: "+e.getMessage()); + } + } + + private static class ConnectionState { + + final ConcurrentMap channelState = new ConcurrentHashMap(); + final Connection connection; + + private ConnectionState(Connection connection) { + this.connection = connection; + } + } + + private static class ChannelState { + + final Lock lock = new ReentrantLock(); + + final Set unackedMessageDeliveryTags = new HashSet<>(); + final Set consumersWithManualAck = new HashSet<>(); + final Set unconfirmedMessageDeliveryTags = new HashSet<>(); + + final Channel channel; + + private ChannelState(Channel channel) { + this.channel = channel; + } + + } + + /** + * Increments connection count. + * The connection object is passed in as complementary information + * and without any guarantee of not being null. + * @param connection the connection that has been created (can be null) + */ + protected abstract void incrementConnectionCount(Connection connection); + + /** + * Decrements connection count. + * The connection object is passed in as complementary information + * and without any guarantee of not being null. + * @param connection the connection that has been closed (can be null) + */ + protected abstract void decrementConnectionCount(Connection connection); + + /** + * Increments channel count. + * The channel object is passed in as complementary information + * and without any guarantee of not being null. + * @param channel the channel that has been created (can be null) + */ + protected abstract void incrementChannelCount(Channel channel); + + /** + * Decrements channel count. + * The channel object is passed in as complementary information + * and without any guarantee of not being null. + * @param channel + */ + protected abstract void decrementChannelCount(Channel channel); + + /** + * Marks the event of a published message. + */ + protected abstract void markPublishedMessage(); + + /** + * Marks the event of a message publishing failure. + */ + protected abstract void markMessagePublishFailed(); + + /** + * Marks the event of a consumed message. + */ + protected abstract void markConsumedMessage(); + + /** + * Marks the event of an acknowledged message. + */ + protected abstract void markAcknowledgedMessage(); + + /** + * Marks the event of a rejected message. + * + * @deprecated Use {@link #markRejectedMessage(boolean)} instead + */ + protected abstract void markRejectedMessage(); + + /** + * Marks the event of a rejected message. + */ + protected void markRejectedMessage(boolean requeue) { + this.markRejectedMessage(); + } + + /** + * Marks the event of a message publishing acknowledgement. + */ + protected abstract void markMessagePublishAcknowledged(); + + /** + * Marks the event of a message publishing not being acknowledged. + */ + protected abstract void markMessagePublishNotAcknowledged(); + /** + * Marks the event of a published message not being routed. + */ + protected abstract void markPublishedMessageUnrouted(); +} diff --git a/src/main/java/com/rabbitmq/client/impl/AnonymousMechanism.java b/src/main/java/com/rabbitmq/client/impl/AnonymousMechanism.java new file mode 100644 index 0000000000..4646ae7fee --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/AnonymousMechanism.java @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom +// Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.LongString; +import com.rabbitmq.client.SaslMechanism; + +/** + * The ANONYMOUS auth mechanism + * + *

Requires RabbitMQ 4.0 or more. + */ +public class AnonymousMechanism implements SaslMechanism { + @Override + public String getName() { + return "ANONYMOUS"; + } + + @Override + public LongString handleChallenge(LongString challenge, String username, String password) { + return LongStringHelper.asLongString(""); + } +} diff --git a/src/com/rabbitmq/client/impl/CRDemoMechanism.java b/src/main/java/com/rabbitmq/client/impl/CRDemoMechanism.java similarity index 59% rename from src/com/rabbitmq/client/impl/CRDemoMechanism.java rename to src/main/java/com/rabbitmq/client/impl/CRDemoMechanism.java index 45fb70053c..fe7638112f 100644 --- a/src/com/rabbitmq/client/impl/CRDemoMechanism.java +++ b/src/main/java/com/rabbitmq/client/impl/CRDemoMechanism.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -34,10 +33,12 @@ public class CRDemoMechanism implements SaslMechanism { private int round = 0; + @Override public String getName() { return NAME; } + @Override public LongString handleChallenge(LongString challenge, String username, String password) { round++; if (round == 1) { @@ -48,6 +49,7 @@ public LongString handleChallenge(LongString challenge, String username, String } public static class CRDemoSaslConfig implements SaslConfig { + @Override public SaslMechanism getSaslMechanism(String[] mechanisms) { if (Arrays.asList(mechanisms).contains(NAME)) { return new CRDemoMechanism(); diff --git a/src/com/rabbitmq/client/impl/ChannelManager.java b/src/main/java/com/rabbitmq/client/impl/ChannelManager.java similarity index 56% rename from src/com/rabbitmq/client/impl/ChannelManager.java rename to src/main/java/com/rabbitmq/client/impl/ChannelManager.java index 85e30a4e20..49f9551b36 100644 --- a/src/com/rabbitmq/client/impl/ChannelManager.java +++ b/src/main/java/com/rabbitmq/client/impl/ChannelManager.java @@ -1,43 +1,48 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.MetricsCollector; +import com.rabbitmq.client.NoOpMetricsCollector; +import com.rabbitmq.client.ShutdownSignalException; +import com.rabbitmq.client.observation.ObservationCollector; +import com.rabbitmq.utility.IntAllocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -import com.rabbitmq.client.ShutdownSignalException; -import com.rabbitmq.utility.IntAllocator; +import java.util.concurrent.*; /** * Manages a set of channels, indexed by channel number (1.._channelMax). */ public class ChannelManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(ChannelManager.class); + /** Monitor for _channelMap and channelNumberAllocator */ private final Object monitor = new Object(); - /** Mapping from 1.._channelMax to {@link ChannelN} instance */ - private final Map _channelMap = new HashMap(); - private final IntAllocator channelNumberAllocator; + /** Mapping from 1.._channelMax to {@link ChannelN} instance */ + private final Map _channelMap = new HashMap(); + private final IntAllocator channelNumberAllocator; private final ConsumerWorkService workService; @@ -45,8 +50,14 @@ public class ChannelManager { /** Maximum channel number available on this connection. */ private final int _channelMax; + private ExecutorService shutdownExecutor; private final ThreadFactory threadFactory; + private int channelShutdownTimeout = (int) ((ConnectionFactory.DEFAULT_HEARTBEAT * AMQConnection.CHANNEL_SHUTDOWN_TIMEOUT_MULTIPLIER) * 1000); + + protected final MetricsCollector metricsCollector; + protected final ObservationCollector observationCollector; + public int getChannelMax(){ return _channelMax; } @@ -56,6 +67,15 @@ public ChannelManager(ConsumerWorkService workService, int channelMax) { } public ChannelManager(ConsumerWorkService workService, int channelMax, ThreadFactory threadFactory) { + this(workService, channelMax, threadFactory, + new NoOpMetricsCollector(), ObservationCollector.NO_OP); + } + + + public ChannelManager(ConsumerWorkService workService, int channelMax, ThreadFactory threadFactory, + MetricsCollector metricsCollector, ObservationCollector observationCollector) { + if (channelMax < 0) + throw new IllegalArgumentException("create ChannelManager: 'channelMax' must be greater or equal to 0."); if (channelMax == 0) { // The framing encoding only allows for unsigned 16-bit integers // for the channel number @@ -66,6 +86,8 @@ public ChannelManager(ConsumerWorkService workService, int channelMax, ThreadFac this.workService = workService; this.threadFactory = threadFactory; + this.metricsCollector = metricsCollector; + this.observationCollector = observationCollector; } /** @@ -86,14 +108,33 @@ public ChannelN getChannel(int channelNumber) { * Handle shutdown. All the managed {@link com.rabbitmq.client.Channel Channel}s are shutdown. * @param signal reason for shutdown */ - public void handleSignal(ShutdownSignalException signal) { + public void handleSignal(final ShutdownSignalException signal) { Set channels; synchronized(this.monitor) { channels = new HashSet(_channelMap.values()); } - for (ChannelN channel : channels) { + + for (final ChannelN channel : channels) { releaseChannelNumber(channel); - channel.processShutdownSignal(signal, true, true); + // async shutdown if possible + // see https://github.com/rabbitmq/rabbitmq-java-client/issues/194 + Runnable channelShutdownRunnable = new Runnable() { + @Override + public void run() { + channel.processShutdownSignal(signal, true, true); + } + }; + if(this.shutdownExecutor == null) { + channelShutdownRunnable.run(); + } else { + Future channelShutdownTask = this.shutdownExecutor.submit(channelShutdownRunnable); + try { + channelShutdownTask.get(channelShutdownTimeout, TimeUnit.MILLISECONDS); + } catch (Exception e) { + LOGGER.warn("Couldn't properly close channel {} on shutdown after waiting for {} ms", channel.getChannelNumber(), channelShutdownTimeout); + channelShutdownTask.cancel(true); + } + } shutdownSet.add(channel.getShutdownLatch()); channel.notifyListeners(); } @@ -104,12 +145,19 @@ private void scheduleShutdownProcessing() { final Set sdSet = new HashSet(shutdownSet); final ConsumerWorkService ssWorkService = workService; Runnable target = new Runnable() { + @Override public void run() { for (CountDownLatch latch : sdSet) { try { int shutdownTimeout = ssWorkService.getShutdownTimeout(); - if (shutdownTimeout == 0) latch.await(); - else latch.await(shutdownTimeout, TimeUnit.MILLISECONDS); + if (shutdownTimeout == 0) { + latch.await(); + } else { + boolean completed = latch.await(shutdownTimeout, TimeUnit.MILLISECONDS); + if (!completed) { + LOGGER.warn("Consumer dispatcher for channel didn't shutdown after waiting for {} ms", shutdownTimeout); + } + } } catch (Throwable e) { /*ignored*/ } @@ -117,8 +165,15 @@ public void run() { ssWorkService.shutdown(); } }; - Thread shutdownThread = Environment.newThread(threadFactory, target, "ConsumerWorkService shutdown monitor", true); - shutdownThread.start(); + if(this.shutdownExecutor != null) { + shutdownExecutor.execute(target); + } else { + Thread shutdownThread = Environment.newThread(threadFactory, + target, + "ConsumerWorkService shutdown monitor", + true); + shutdownThread.start(); + } } public ChannelN createChannel(AMQConnection connection) throws IOException { @@ -148,7 +203,7 @@ public ChannelN createChannel(AMQConnection connection, int channelNumber) throw return ch; } - private ChannelN addNewChannel(AMQConnection connection, int channelNumber) throws IOException { + private ChannelN addNewChannel(AMQConnection connection, int channelNumber) { if (_channelMap.containsKey(channelNumber)) { // That number's already allocated! Can't do it // This should never happen unless something has gone @@ -164,7 +219,8 @@ private ChannelN addNewChannel(AMQConnection connection, int channelNumber) thro } protected ChannelN instantiateChannel(AMQConnection connection, int channelNumber, ConsumerWorkService workService) { - return new ChannelN(connection, channelNumber, workService); + return new ChannelN(connection, channelNumber, workService, + this.metricsCollector, this.observationCollector); } /** @@ -199,4 +255,24 @@ else if (existing != channel) { channelNumberAllocator.free(channelNumber); } } + + public ExecutorService getShutdownExecutor() { + return shutdownExecutor; + } + + public void setShutdownExecutor(ExecutorService shutdownExecutor) { + this.shutdownExecutor = shutdownExecutor; + } + + /** + * Set the shutdown timeout for channels. + * This is the amount of time the manager waits for a channel to + * shutdown before giving up. + * Works only when the {@code shutdownExecutor} property is set. + * Default to {@link com.rabbitmq.client.ConnectionFactory#DEFAULT_HEARTBEAT} + 5 % seconds + * @param channelShutdownTimeout shutdown timeout in milliseconds + */ + public void setChannelShutdownTimeout(int channelShutdownTimeout) { + this.channelShutdownTimeout = channelShutdownTimeout; + } } diff --git a/src/com/rabbitmq/client/impl/ChannelN.java b/src/main/java/com/rabbitmq/client/impl/ChannelN.java similarity index 62% rename from src/com/rabbitmq/client/impl/ChannelN.java rename to src/main/java/com/rabbitmq/client/impl/ChannelN.java index 481dbf51bc..d97d6f2614 100644 --- a/src/com/rabbitmq/client/impl/ChannelN.java +++ b/src/main/java/com/rabbitmq/client/impl/ChannelN.java @@ -1,52 +1,38 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeoutException; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.AMQP.BasicProperties; -import com.rabbitmq.client.Command; -import com.rabbitmq.client.ConfirmListener; +import com.rabbitmq.client.*; import com.rabbitmq.client.Connection; -import com.rabbitmq.client.Consumer; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.FlowListener; -import com.rabbitmq.client.GetResponse; import com.rabbitmq.client.Method; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.ReturnListener; -import com.rabbitmq.client.ShutdownSignalException; -import com.rabbitmq.client.UnexpectedMethodError; -import com.rabbitmq.client.impl.AMQImpl.Basic; +import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.impl.AMQImpl.Channel; -import com.rabbitmq.client.impl.AMQImpl.Confirm; -import com.rabbitmq.client.impl.AMQImpl.Exchange; import com.rabbitmq.client.impl.AMQImpl.Queue; -import com.rabbitmq.client.impl.AMQImpl.Tx; +import com.rabbitmq.client.impl.AMQImpl.*; +import com.rabbitmq.client.observation.ObservationCollector; import com.rabbitmq.utility.Utility; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeoutException; /** * Main interface to AMQP protocol functionality. Public API - @@ -59,7 +45,9 @@ * */ public class ChannelN extends AMQChannel implements com.rabbitmq.client.Channel { + private static final int MAX_UNSIGNED_SHORT = 65535; private static final String UNSPECIFIED_OUT_OF_BAND = ""; + private static final Logger LOGGER = LoggerFactory.getLogger(ChannelN.class); /** Map from consumer tag to {@link Consumer} instance. *

@@ -75,8 +63,6 @@ public class ChannelN extends AMQChannel implements com.rabbitmq.client.Channel /* All listeners collections are in CopyOnWriteArrayList objects */ /** The ReturnListener collection. */ private final Collection returnListeners = new CopyOnWriteArrayList(); - /** The FlowListener collection. */ - private final Collection flowListeners = new CopyOnWriteArrayList(); /** The ConfirmListener collection. */ private final Collection confirmListeners = new CopyOnWriteArrayList(); @@ -97,9 +83,15 @@ public class ChannelN extends AMQChannel implements com.rabbitmq.client.Channel private final SortedSet unconfirmedSet = Collections.synchronizedSortedSet(new TreeSet()); + /** Whether the confirm select method has been successfully activated */ + private boolean confirmSelectActivated = false; + /** Whether any nacks have been received since the last waitForConfirms(). */ private volatile boolean onlyAcksReceived = true; + protected final MetricsCollector metricsCollector; + private final ObservationCollector observationCollector; + /** * Construct a new channel on the given connection with the given * channel number. Usually not called directly - call @@ -111,8 +103,27 @@ public class ChannelN extends AMQChannel implements com.rabbitmq.client.Channel */ public ChannelN(AMQConnection connection, int channelNumber, ConsumerWorkService workService) { + this(connection, channelNumber, workService, + new NoOpMetricsCollector(), ObservationCollector.NO_OP); + } + + /** + * Construct a new channel on the given connection with the given + * channel number. Usually not called directly - call + * Connection.createChannel instead. + * @see Connection#createChannel + * @param connection The connection associated with this channel + * @param channelNumber The channel number to be associated with this channel + * @param workService service for managing this channel's consumer callbacks + * @param metricsCollector service for managing metrics + */ + public ChannelN(AMQConnection connection, int channelNumber, + ConsumerWorkService workService, + MetricsCollector metricsCollector, ObservationCollector observationCollector) { super(connection, channelNumber); this.dispatcher = new ConsumerDispatcher(connection, this, workService); + this.metricsCollector = metricsCollector; + this.observationCollector = observationCollector; } /** @@ -125,43 +136,65 @@ public void open() throws IOException { exnWrappingRpc(new Channel.Open(UNSPECIFIED_OUT_OF_BAND)); } + @Override public void addReturnListener(ReturnListener listener) { returnListeners.add(listener); } + @Override + public ReturnListener addReturnListener(ReturnCallback returnCallback) { + ReturnListener returnListener = (replyCode, replyText, exchange, routingKey, properties, body) -> returnCallback.handle(new Return( + replyCode, replyText, exchange, routingKey, properties, body + )); + this.addReturnListener(returnListener); + return returnListener; + } + + @Override public boolean removeReturnListener(ReturnListener listener) { return returnListeners.remove(listener); } + @Override public void clearReturnListeners() { returnListeners.clear(); } - public void addFlowListener(FlowListener listener) { - flowListeners.add(listener); + @Override + public void addConfirmListener(ConfirmListener listener) { + confirmListeners.add(listener); } - public boolean removeFlowListener(FlowListener listener) { - return flowListeners.remove(listener); - } + @Override + public ConfirmListener addConfirmListener(ConfirmCallback ackCallback, ConfirmCallback nackCallback) { + ConfirmListener confirmListener = new ConfirmListener() { - public void clearFlowListeners() { - flowListeners.clear(); - } + @Override + public void handleAck(long deliveryTag, boolean multiple) throws IOException { + ackCallback.handle(deliveryTag, multiple); + } - public void addConfirmListener(ConfirmListener listener) { - confirmListeners.add(listener); + @Override + public void handleNack(long deliveryTag, boolean multiple) throws IOException { + nackCallback.handle(deliveryTag, multiple); + } + }; + this.addConfirmListener(confirmListener); + return confirmListener; } + @Override public boolean removeConfirmListener(ConfirmListener listener) { return confirmListeners.remove(listener); } + @Override public void clearConfirmListeners() { confirmListeners.clear(); } /** {@inheritDoc} */ + @Override public boolean waitForConfirms() throws InterruptedException { @@ -173,6 +206,7 @@ public boolean waitForConfirms() } /** {@inheritDoc} */ + @Override public boolean waitForConfirms(long timeout) throws InterruptedException, TimeoutException { if (nextPublishSeqNo == 0L) @@ -203,6 +237,7 @@ public boolean waitForConfirms(long timeout) } /** {@inheritDoc} */ + @Override public void waitForConfirmsOrDie() throws IOException, InterruptedException { @@ -212,6 +247,7 @@ public void waitForConfirmsOrDie() } /** {@inheritDoc} */ + @Override public void waitForConfirmsOrDie(long timeout) throws IOException, InterruptedException, TimeoutException { @@ -227,6 +263,7 @@ public void waitForConfirmsOrDie(long timeout) } /** Returns the current default consumer. */ + @Override public Consumer getDefaultConsumer() { return defaultConsumer; } @@ -235,6 +272,7 @@ public Consumer getDefaultConsumer() { * Sets the current default consumer. * A null argument is interpreted to mean "do not use a default consumer". */ + @Override public void setDefaultConsumer(Consumer consumer) { defaultConsumer = consumer; } @@ -245,11 +283,7 @@ public void setDefaultConsumer(Consumer consumer) { * @param signal an exception signalling channel shutdown */ private void broadcastShutdownSignal(ShutdownSignalException signal) { - Map snapshotConsumers; - synchronized (_consumers) { - snapshotConsumers = new HashMap(_consumers); - } - this.finishedShutdownFlag = this.dispatcher.handleShutdownSignal(snapshotConsumers, signal); + this.finishedShutdownFlag = this.dispatcher.handleShutdownSignal(Utility.copy(_consumers), signal); } /** @@ -327,12 +361,14 @@ private void releaseChannel() { return true; } else if (method instanceof Channel.Flow) { Channel.Flow channelFlow = (Channel.Flow) method; - synchronized (_channelMutex) { + _channelLock.lock(); + try { _blockContent = !channelFlow.getActive(); transmit(new Channel.FlowOk(!_blockContent)); - _channelMutex.notifyAll(); + _channelLockCondition.signalAll(); + } finally { + _channelLock.unlock(); } - callFlowListeners(command, channelFlow); return true; } else if (method instanceof Basic.Ack) { Basic.Ack ack = (Basic.Ack) method; @@ -345,7 +381,7 @@ private void releaseChannel() { handleAckNack(nack.getDeliveryTag(), nack.getMultiple(), true); return true; } else if (method instanceof Basic.RecoverOk) { - for (Map.Entry entry : _consumers.entrySet()) { + for (Map.Entry entry : Utility.copy(_consumers).entrySet()) { this.dispatcher.handleRecoverOk(entry.getValue(), entry.getKey()); } // Unlike all the other cases we still want this RecoverOk to @@ -356,12 +392,19 @@ private void releaseChannel() { Basic.Cancel m = (Basic.Cancel)method; String consumerTag = m.getConsumerTag(); Consumer callback = _consumers.remove(consumerTag); + // Not finding any matching consumer isn't necessarily an indication of an issue anywhere. + // Sometimes there's a natural race condition between consumer management on the server and client ends. + // E.g. Channel#basicCancel called just before a basic.cancel for the same consumer tag is received. + // See https://github.com/rabbitmq/rabbitmq-java-client/issues/525 if (callback == null) { callback = defaultConsumer; } if (callback != null) { try { this.dispatcher.handleCancel(callback, consumerTag); + } catch (WorkPoolFullException e) { + // couldn't enqueue in work pool, propagating + throw e; } catch (Throwable ex) { getConnection().getExceptionHandler().handleConsumerException(this, ex, @@ -369,6 +412,8 @@ private void releaseChannel() { consumerTag, "handleCancel"); } + } else { + LOGGER.warn("Could not cancel consumer with unknown tag {}", consumerTag); } return true; } else { @@ -415,17 +460,24 @@ protected void processDelivery(Command command, Basic.Deliver method) { m.getExchange(), m.getRoutingKey()); try { + // call metricsCollector before the dispatching (which is async anyway) + // this way, the message is inside the stats before it is handled + // in case a manual ack in the callback, the stats will be able to record the ack + metricsCollector.consumedMessage(this, m.getDeliveryTag(), m.getConsumerTag()); this.dispatcher.handleDelivery(callback, m.getConsumerTag(), envelope, (BasicProperties) command.getContentHeader(), command.getContentBody()); + } catch (WorkPoolFullException e) { + // couldn't enqueue in work pool, propagating + throw e; } catch (Throwable ex) { getConnection().getExceptionHandler().handleConsumerException(this, - ex, - callback, - m.getConsumerTag(), - "handleDelivery"); + ex, + callback, + m.getConsumerTag(), + "handleDelivery"); } } @@ -433,24 +485,16 @@ private void callReturnListeners(Command command, Basic.Return basicReturn) { try { for (ReturnListener l : this.returnListeners) { l.handleReturn(basicReturn.getReplyCode(), - basicReturn.getReplyText(), - basicReturn.getExchange(), - basicReturn.getRoutingKey(), - (BasicProperties) command.getContentHeader(), - command.getContentBody()); + basicReturn.getReplyText(), + basicReturn.getExchange(), + basicReturn.getRoutingKey(), + (BasicProperties) command.getContentHeader(), + command.getContentBody()); } } catch (Throwable ex) { getConnection().getExceptionHandler().handleReturnListenerException(this, ex); - } - } - - private void callFlowListeners(@SuppressWarnings("unused") Command command, Channel.Flow channelFlow) { - try { - for (FlowListener l : this.flowListeners) { - l.handleFlow(channelFlow.getActive()); - } - } catch (Throwable ex) { - getConnection().getExceptionHandler().handleFlowListenerException(this, ex); + } finally { + metricsCollector.basicPublishUnrouted(this); } } @@ -461,6 +505,8 @@ private void callConfirmListeners(@SuppressWarnings("unused") Command command, B } } catch (Throwable ex) { getConnection().getExceptionHandler().handleConfirmListenerException(this, ex); + } finally { + metricsCollector.basicPublishAck(this, ack.getDeliveryTag(), ack.getMultiple()); } } @@ -471,6 +517,8 @@ private void callConfirmListeners(@SuppressWarnings("unused") Command command, B } } catch (Throwable ex) { getConnection().getExceptionHandler().handleConfirmListenerException(this, ex); + } finally { + metricsCollector.basicPublishNack(this, nack.getDeliveryTag(), nack.getMultiple()); } } @@ -479,7 +527,8 @@ private void asyncShutdown(Command command) throws IOException { false, command.getMethod(), this); - synchronized (_channelMutex) { + _channelLock.lock(); + try { try { processShutdownSignal(signal, true, false); quiescingTransmit(new Channel.CloseOk()); @@ -488,37 +537,42 @@ private void asyncShutdown(Command command) throws IOException { notifyOutstandingRpc(signal); } } + finally { + _channelLock.unlock(); + } notifyListeners(); } /** Public API - {@inheritDoc} */ + @Override public void close() - throws IOException - { + throws IOException, TimeoutException { close(AMQP.REPLY_SUCCESS, "OK"); } /** Public API - {@inheritDoc} */ + @Override public void close(int closeCode, String closeMessage) - throws IOException - { + throws IOException, TimeoutException { close(closeCode, closeMessage, true, null, false); } /** Public API - {@inheritDoc} */ + @Override public void abort() - throws IOException { abort(AMQP.REPLY_SUCCESS, "OK"); } /** Public API - {@inheritDoc} */ + @Override public void abort(int closeCode, String closeMessage) - throws IOException { try { - close(closeCode, closeMessage, true, null, true); - } catch (IOException _e) { /* ignored */ } + close(closeCode, closeMessage, true, null, true); + } catch (IOException | TimeoutException _e) { + // abort() shall silently discard any exceptions + } } /** @@ -537,8 +591,7 @@ protected void close(int closeCode, boolean initiatedByApplication, Throwable cause, boolean abort) - throws IOException - { + throws IOException, TimeoutException { // First, notify all our dependents that we are shutting down. // This clears isOpen(), so no further work from the // application side will be accepted, and any inbound commands @@ -561,19 +614,24 @@ public AMQCommand transformReply(AMQCommand command) { boolean notify = false; try { // Synchronize the block below to avoid race conditions in case - // connnection wants to send Connection-CloseOK - synchronized (_channelMutex) { + // connection wants to send Connection-CloseOK + _channelLock.lock(); + try { startProcessShutdownSignal(signal, !initiatedByApplication, true); quiescingRpc(reason, k); + } finally { + _channelLock.unlock(); } // Now that we're in quiescing state, channel.close was sent and // we wait for the reply. We ignore the result. // (It's NOT always close-ok.) notify = true; - k.getReply(-1); + // do not wait indefinitely + k.getReply(10000); } catch (TimeoutException ise) { - // Will never happen since we wait infinitely + if (!abort) + throw ise; } catch (ShutdownSignalException sse) { if (!abort) throw sse; @@ -594,13 +652,18 @@ public AMQCommand transformReply(AMQCommand command) { } /** Public API - {@inheritDoc} */ + @Override public void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException { - exnWrappingRpc(new Basic.Qos(prefetchSize, prefetchCount, global)); + if (prefetchCount < 0 || prefetchCount > MAX_UNSIGNED_SHORT) { + throw new IllegalArgumentException("Prefetch count must be between 0 and " + MAX_UNSIGNED_SHORT); + } + exnWrappingRpc(new Basic.Qos(prefetchSize, prefetchCount, global)); } /** Public API - {@inheritDoc} */ + @Override public void basicQos(int prefetchCount, boolean global) throws IOException { @@ -608,6 +671,7 @@ public void basicQos(int prefetchCount, boolean global) } /** Public API - {@inheritDoc} */ + @Override public void basicQos(int prefetchCount) throws IOException { @@ -615,6 +679,7 @@ public void basicQos(int prefetchCount) } /** Public API - {@inheritDoc} */ + @Override public void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException @@ -623,6 +688,7 @@ public void basicPublish(String exchange, String routingKey, } /** Public API - {@inheritDoc} */ + @Override public void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body) @@ -632,29 +698,44 @@ public void basicPublish(String exchange, String routingKey, } /** Public API - {@inheritDoc} */ + @Override public void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body) throws IOException { + final long deliveryTag; if (nextPublishSeqNo > 0) { - unconfirmedSet.add(getNextPublishSeqNo()); + deliveryTag = getNextPublishSeqNo(); + unconfirmedSet.add(deliveryTag); nextPublishSeqNo++; + } else { + deliveryTag = 0; } - BasicProperties useProps = props; if (props == null) { - useProps = MessageProperties.MINIMAL_BASIC; + props = MessageProperties.MINIMAL_BASIC; } - transmit(new AMQCommand(new Basic.Publish.Builder() - .exchange(exchange) - .routingKey(routingKey) - .mandatory(mandatory) - .immediate(immediate) - .build(), - useProps, body)); + AMQP.Basic.Publish publish = new Basic.Publish.Builder() + .exchange(exchange) + .routingKey(routingKey) + .mandatory(mandatory) + .immediate(immediate) + .build(); + try { + ObservationCollector.PublishCall publishCall = properties -> { + AMQCommand command = new AMQCommand(publish, properties, body); + transmit(command); + }; + observationCollector.publish(publishCall, publish, props, body, this.connectionInfo()); + } catch (IOException | AlreadyClosedException e) { + metricsCollector.basicPublishFailure(this, e); + throw e; + } + metricsCollector.basicPublish(this, deliveryTag); } /** Public API - {@inheritDoc} */ + @Override public Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map arguments) @@ -665,6 +746,19 @@ public Exchange.DeclareOk exchangeDeclare(String exchange, String type, arguments); } + /** Public API - {@inheritDoc} */ + @Override + public Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, + boolean durable, boolean autoDelete, + Map arguments) + throws IOException + { + return exchangeDeclare(exchange, type.getType(), + durable, autoDelete, + arguments); + } + + @Override public void exchangeDeclareNoWait(String exchange, String type, boolean durable, @@ -683,7 +777,20 @@ public void exchangeDeclareNoWait(String exchange, .build())); } + @Override + public void exchangeDeclareNoWait(String exchange, + BuiltinExchangeType type, + boolean durable, + boolean autoDelete, + boolean internal, + Map arguments) throws IOException { + exchangeDeclareNoWait(exchange, type.getType(), + durable, autoDelete, internal, + arguments); + } + /** Public API - {@inheritDoc} */ + @Override public Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, @@ -704,6 +811,21 @@ public Exchange.DeclareOk exchangeDeclare(String exchange, String type, } /** Public API - {@inheritDoc} */ + @Override + public Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, + boolean durable, + boolean autoDelete, + boolean internal, + Map arguments) + throws IOException + { + return exchangeDeclare(exchange, type.getType(), + durable, autoDelete, internal, + arguments); + } + + /** Public API - {@inheritDoc} */ + @Override public Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable) throws IOException @@ -712,6 +834,16 @@ public Exchange.DeclareOk exchangeDeclare(String exchange, String type, } /** Public API - {@inheritDoc} */ + @Override + public Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, + boolean durable) + throws IOException + { + return exchangeDeclare(exchange, type.getType(), durable); + } + + /** Public API - {@inheritDoc} */ + @Override public Exchange.DeclareOk exchangeDeclare(String exchange, String type) throws IOException { @@ -719,6 +851,15 @@ public Exchange.DeclareOk exchangeDeclare(String exchange, String type) } /** Public API - {@inheritDoc} */ + @Override + public Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type) + throws IOException + { + return exchangeDeclare(exchange, type.getType()); + } + + /** Public API - {@inheritDoc} */ + @Override public Exchange.DeclareOk exchangeDeclarePassive(String exchange) throws IOException { @@ -732,6 +873,7 @@ public Exchange.DeclareOk exchangeDeclarePassive(String exchange) } /** Public API - {@inheritDoc} */ + @Override public Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) throws IOException { @@ -744,6 +886,7 @@ public Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) } /** Public API - {@inheritDoc} */ + @Override public void exchangeDeleteNoWait(String exchange, boolean ifUnused) throws IOException { transmit(new AMQCommand(new Exchange.Delete.Builder() .exchange(exchange) @@ -753,6 +896,7 @@ public void exchangeDeleteNoWait(String exchange, boolean ifUnused) throws IOExc } /** Public API - {@inheritDoc} */ + @Override public Exchange.DeleteOk exchangeDelete(String exchange) throws IOException { @@ -760,6 +904,7 @@ public Exchange.DeleteOk exchangeDelete(String exchange) } /** Public API - {@inheritDoc} */ + @Override public Exchange.BindOk exchangeBind(String destination, String source, String routingKey, Map arguments) throws IOException { @@ -774,6 +919,7 @@ public Exchange.BindOk exchangeBind(String destination, String source, } /** Public API - {@inheritDoc} */ + @Override public void exchangeBindNoWait(String destination, String source, String routingKey, @@ -788,12 +934,14 @@ public void exchangeBindNoWait(String destination, } /** Public API - {@inheritDoc} */ + @Override public Exchange.BindOk exchangeBind(String destination, String source, String routingKey) throws IOException { return exchangeBind(destination, source, routingKey, null); } /** Public API - {@inheritDoc} */ + @Override public Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey, Map arguments) throws IOException { @@ -808,12 +956,14 @@ public Exchange.UnbindOk exchangeUnbind(String destination, String source, } /** Public API - {@inheritDoc} */ + @Override public Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey) throws IOException { return exchangeUnbind(destination, source, routingKey, null); } /** Public API - {@inheritDoc} */ + @Override public void exchangeUnbindNoWait(String destination, String source, String routingKey, Map arguments) throws IOException { @@ -827,10 +977,12 @@ public void exchangeUnbindNoWait(String destination, String source, } /** Public API - {@inheritDoc} */ + @Override public Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments) throws IOException { + validateQueueNameLength(queue); return (Queue.DeclareOk) exnWrappingRpc(new Queue.Declare.Builder() .queue(queue) @@ -843,6 +995,7 @@ public Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclu } /** Public API - {@inheritDoc} */ + @Override public com.rabbitmq.client.AMQP.Queue.DeclareOk queueDeclare() throws IOException { @@ -850,11 +1003,13 @@ public com.rabbitmq.client.AMQP.Queue.DeclareOk queueDeclare() } /** Public API - {@inheritDoc} */ + @Override public void queueDeclareNoWait(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments) throws IOException { + validateQueueNameLength(queue); transmit(new AMQCommand(new Queue.Declare.Builder() .queue(queue) .durable(durable) @@ -867,9 +1022,11 @@ public void queueDeclareNoWait(String queue, } /** Public API - {@inheritDoc} */ + @Override public Queue.DeclareOk queueDeclarePassive(String queue) throws IOException { + validateQueueNameLength(queue); return (Queue.DeclareOk) exnWrappingRpc(new Queue.Declare.Builder() .queue(queue) @@ -881,9 +1038,25 @@ public Queue.DeclareOk queueDeclarePassive(String queue) } /** Public API - {@inheritDoc} */ + @Override + public long messageCount(String queue) throws IOException { + Queue.DeclareOk ok = queueDeclarePassive(queue); + return ok.getMessageCount(); + } + + /** Public API - {@inheritDoc} */ + @Override + public long consumerCount(String queue) throws IOException { + Queue.DeclareOk ok = queueDeclarePassive(queue); + return ok.getConsumerCount(); + } + + /** Public API - {@inheritDoc} */ + @Override public Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty) throws IOException { + validateQueueNameLength(queue); return (Queue.DeleteOk) exnWrappingRpc(new Queue.Delete.Builder() .queue(queue) @@ -895,6 +1068,7 @@ public Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpt @Override public void queueDeleteNoWait(String queue, boolean ifUnused, boolean ifEmpty) throws IOException { + validateQueueNameLength(queue); transmit(new AMQCommand(new Queue.Delete.Builder() .queue(queue) .ifUnused(ifUnused) @@ -904,6 +1078,7 @@ public void queueDeleteNoWait(String queue, boolean ifUnused, boolean ifEmpty) t } /** Public API - {@inheritDoc} */ + @Override public Queue.DeleteOk queueDelete(String queue) throws IOException { @@ -911,10 +1086,12 @@ public Queue.DeleteOk queueDelete(String queue) } /** Public API - {@inheritDoc} */ + @Override public Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map arguments) throws IOException { + validateQueueNameLength(queue); return (Queue.BindOk) exnWrappingRpc(new Queue.Bind.Builder() .queue(queue) @@ -926,31 +1103,36 @@ public Queue.BindOk queueBind(String queue, String exchange, } /** Public API - {@inheritDoc} */ + @Override public Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOException { - return queueBind(queue, exchange, routingKey, null); } /** Public API - {@inheritDoc} */ + @Override public void queueBindNoWait(String queue, String exchange, String routingKey, Map arguments) throws IOException { + validateQueueNameLength(queue); transmit(new AMQCommand(new Queue.Bind.Builder() .queue(queue) .exchange(exchange) .routingKey(routingKey) .arguments(arguments) + .nowait(true) .build())); } /** Public API - {@inheritDoc} */ + @Override public Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey, Map arguments) throws IOException { + validateQueueNameLength(queue); return (Queue.UnbindOk) exnWrappingRpc(new Queue.Unbind.Builder() .queue(queue) @@ -962,9 +1144,11 @@ public Queue.UnbindOk queueUnbind(String queue, String exchange, String routingK } /** Public API - {@inheritDoc} */ + @Override public Queue.PurgeOk queuePurge(String queue) throws IOException { + validateQueueNameLength(queue); return (Queue.PurgeOk) exnWrappingRpc(new Queue.Purge.Builder() .queue(queue) @@ -973,6 +1157,7 @@ public Queue.PurgeOk queuePurge(String queue) } /** Public API - {@inheritDoc} */ + @Override public Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey) throws IOException { @@ -980,54 +1165,68 @@ public Queue.UnbindOk queueUnbind(String queue, String exchange, String routingK } /** Public API - {@inheritDoc} */ + @Override public GetResponse basicGet(String queue, boolean autoAck) throws IOException { + validateQueueNameLength(queue); AMQCommand replyCommand = exnWrappingRpc(new Basic.Get.Builder() .queue(queue) .noAck(autoAck) .build()); - Method method = replyCommand.getMethod(); - - if (method instanceof Basic.GetOk) { - Basic.GetOk getOk = (Basic.GetOk)method; - Envelope envelope = new Envelope(getOk.getDeliveryTag(), - getOk.getRedelivered(), - getOk.getExchange(), - getOk.getRoutingKey()); - BasicProperties props = (BasicProperties)replyCommand.getContentHeader(); - byte[] body = replyCommand.getContentBody(); - int messageCount = getOk.getMessageCount(); - return new GetResponse(envelope, props, body, messageCount); - } else if (method instanceof Basic.GetEmpty) { - return null; - } else { - throw new UnexpectedMethodError(method); - } + return this.observationCollector.basicGet(() -> { + Method method = replyCommand.getMethod(); + + if (method instanceof Basic.GetOk) { + Basic.GetOk getOk = (Basic.GetOk)method; + Envelope envelope = new Envelope(getOk.getDeliveryTag(), + getOk.getRedelivered(), + getOk.getExchange(), + getOk.getRoutingKey()); + BasicProperties props = (BasicProperties)replyCommand.getContentHeader(); + byte[] body = replyCommand.getContentBody(); + int messageCount = getOk.getMessageCount(); + + metricsCollector.consumedMessage(this, getOk.getDeliveryTag(), autoAck); + + return new GetResponse(envelope, props, body, messageCount); + } else if (method instanceof Basic.GetEmpty) { + return null; + } else { + throw new UnexpectedMethodError(method); + } + }, queue); } /** Public API - {@inheritDoc} */ + @Override public void basicAck(long deliveryTag, boolean multiple) throws IOException { transmit(new Basic.Ack(deliveryTag, multiple)); + metricsCollector.basicAck(this, deliveryTag, multiple); } /** Public API - {@inheritDoc} */ + @Override public void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException { transmit(new Basic.Nack(deliveryTag, multiple, requeue)); + metricsCollector.basicNack(this, deliveryTag, requeue); } /** Public API - {@inheritDoc} */ + @Override public void basicReject(long deliveryTag, boolean requeue) throws IOException { transmit(new Basic.Reject(deliveryTag, requeue)); + metricsCollector.basicReject(this, deliveryTag, requeue); } /** Public API - {@inheritDoc} */ + @Override public String basicConsume(String queue, Consumer callback) throws IOException { @@ -1035,6 +1234,26 @@ public String basicConsume(String queue, Consumer callback) } /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException { + return basicConsume(queue, consumerFromDeliverCancelCallbacks(deliverCallback, cancelCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, consumerFromDeliverShutdownCallbacks(deliverCallback, shutdownSignalCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, DeliverCallback deliverCallback, CancelCallback cancelCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, consumerFromDeliverCancelShutdownCallbacks(deliverCallback, cancelCallback, shutdownSignalCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override public String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException { @@ -1042,6 +1261,26 @@ public String basicConsume(String queue, boolean autoAck, Consumer callback) } /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) + throws IOException { + return basicConsume(queue, autoAck, "", consumerFromDeliverShutdownCallbacks(deliverCallback, shutdownSignalCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException { + return basicConsume(queue, autoAck, "", consumerFromDeliverCancelCallbacks(deliverCallback, cancelCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, "", consumerFromDeliverCancelShutdownCallbacks(deliverCallback, cancelCallback, shutdownSignalCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override public String basicConsume(String queue, boolean autoAck, Map arguments, Consumer callback) throws IOException @@ -1050,6 +1289,28 @@ public String basicConsume(String queue, boolean autoAck, Map ar } /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback) + throws IOException { + return basicConsume(queue, autoAck, "", false, false, arguments, consumerFromDeliverCancelCallbacks(deliverCallback, cancelCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, "", false, false, arguments, consumerFromDeliverShutdownCallbacks(deliverCallback, shutdownSignalCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, "", false, false, arguments, consumerFromDeliverCancelShutdownCallbacks(deliverCallback, cancelCallback, shutdownSignalCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override public String basicConsume(String queue, boolean autoAck, String consumerTag, Consumer callback) throws IOException @@ -1058,65 +1319,220 @@ public String basicConsume(String queue, boolean autoAck, String consumerTag, } /** Public API - {@inheritDoc} */ - public String basicConsume(String queue, boolean autoAck, String consumerTag, + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, CancelCallback cancelCallback) + throws IOException { + return basicConsume(queue, autoAck, consumerTag, false, false, null, consumerFromDeliverCancelCallbacks(deliverCallback, cancelCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, consumerTag, false, false, null, consumerFromDeliverShutdownCallbacks(deliverCallback, shutdownSignalCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, CancelCallback cancelCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, consumerTag , false, false, null, consumerFromDeliverCancelShutdownCallbacks(deliverCallback, cancelCallback, shutdownSignalCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, + DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException { + return basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, consumerFromDeliverCancelCallbacks(deliverCallback, cancelCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, + DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, consumerFromDeliverShutdownCallbacks(deliverCallback, shutdownSignalCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, + DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, consumerFromDeliverCancelShutdownCallbacks(deliverCallback, cancelCallback, shutdownSignalCallback)); + } + + /** Public API - {@inheritDoc} */ + @Override + public String basicConsume(String queue, final boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, final Consumer callback) throws IOException { - BlockingRpcContinuation k = new BlockingRpcContinuation() { + final Method m = new Basic.Consume.Builder() + .queue(queue) + .consumerTag(consumerTag) + .noLocal(noLocal) + .noAck(autoAck) + .exclusive(exclusive) + .arguments(arguments) + .build(); + BlockingRpcContinuation k = new BlockingRpcContinuation(m) { + @Override public String transformReply(AMQCommand replyCommand) { String actualConsumerTag = ((Basic.ConsumeOk) replyCommand.getMethod()).getConsumerTag(); - _consumers.put(actualConsumerTag, callback); + Consumer wrappedCallback = observationCollector.basicConsume(queue, consumerTag, callback); + _consumers.put(actualConsumerTag, wrappedCallback); + + // need to register consumer in stats before it actually starts consuming + metricsCollector.basicConsume(ChannelN.this, actualConsumerTag, autoAck); - dispatcher.handleConsumeOk(callback, actualConsumerTag); + dispatcher.handleConsumeOk(wrappedCallback, actualConsumerTag); return actualConsumerTag; } }; - rpc(new Basic.Consume.Builder() - .queue(queue) - .consumerTag(consumerTag) - .noLocal(noLocal) - .noAck(autoAck) - .exclusive(exclusive) - .arguments(arguments) - .build(), - k); + + rpc(m, k); try { - return k.getReply(); + if(_rpcTimeout == NO_RPC_TIMEOUT) { + return k.getReply(); + } else { + try { + return k.getReply(_rpcTimeout); + } catch (TimeoutException e) { + throw wrapTimeoutException(m, e); + } + } } catch(ShutdownSignalException ex) { throw wrap(ex); } } + private Consumer consumerFromDeliverCancelCallbacks(final DeliverCallback deliverCallback, final CancelCallback cancelCallback) { + return new Consumer() { + + @Override + public void handleConsumeOk(String consumerTag) { } + + @Override + public void handleCancelOk(String consumerTag) { } + + @Override + public void handleCancel(String consumerTag) throws IOException { + cancelCallback.handle(consumerTag); + } + + @Override + public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { } + + @Override + public void handleRecoverOk(String consumerTag) { } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { + deliverCallback.handle(consumerTag, new Delivery(envelope, properties, body)); + } + }; + } + + private Consumer consumerFromDeliverShutdownCallbacks(final DeliverCallback deliverCallback, final ConsumerShutdownSignalCallback shutdownSignalCallback) { + return new Consumer() { + @Override + public void handleConsumeOk(String consumerTag) { } + + @Override + public void handleCancelOk(String consumerTag) { } + + @Override + public void handleCancel(String consumerTag) throws IOException { } + + @Override + public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { + shutdownSignalCallback.handleShutdownSignal(consumerTag, sig); + } + + @Override + public void handleRecoverOk(String consumerTag) { } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { + deliverCallback.handle(consumerTag, new Delivery(envelope, properties, body)); + } + }; + } + + private Consumer consumerFromDeliverCancelShutdownCallbacks(final DeliverCallback deliverCallback, final CancelCallback cancelCallback, final ConsumerShutdownSignalCallback shutdownSignalCallback) { + return new Consumer() { + @Override + public void handleConsumeOk(String consumerTag) { } + + @Override + public void handleCancelOk(String consumerTag) { } + + @Override + public void handleCancel(String consumerTag) throws IOException { + cancelCallback.handle(consumerTag); + } + + @Override + public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { + shutdownSignalCallback.handleShutdownSignal(consumerTag, sig); + } + + @Override + public void handleRecoverOk(String consumerTag) { } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { + deliverCallback.handle(consumerTag, new Delivery(envelope, properties, body)); + } + }; + } + /** Public API - {@inheritDoc} */ + @Override public void basicCancel(final String consumerTag) throws IOException { final Consumer originalConsumer = _consumers.get(consumerTag); - if (originalConsumer == null) - throw new IOException("Unknown consumerTag"); - BlockingRpcContinuation k = new BlockingRpcContinuation() { + if (originalConsumer == null) { + LOGGER.warn("Tried to cancel consumer with unknown tag {}", consumerTag); + return; + } + + final Method m = new Basic.Cancel(consumerTag, false); + BlockingRpcContinuation k = new BlockingRpcContinuation(m) { + @Override public Consumer transformReply(AMQCommand replyCommand) { - replyCommand.getMethod(); + if (!(replyCommand.getMethod() instanceof Basic.CancelOk)) + LOGGER.warn("Received reply {} was not of expected method Basic.CancelOk", replyCommand.getMethod()); _consumers.remove(consumerTag); //may already have been removed dispatcher.handleCancelOk(originalConsumer, consumerTag); return originalConsumer; } }; - rpc(new Basic.Cancel(consumerTag, false), k); + + rpc(m, k); try { - k.getReply(); // discard result + if(_rpcTimeout == NO_RPC_TIMEOUT) { + k.getReply(); // discard result + } else { + try { + k.getReply(_rpcTimeout); + } catch (TimeoutException e) { + throw wrapTimeoutException(m, e); + } + } } catch(ShutdownSignalException ex) { throw wrap(ex); } + metricsCollector.basicCancel(this, consumerTag); } /** Public API - {@inheritDoc} */ + @Override public Basic.RecoverOk basicRecover() throws IOException { @@ -1124,6 +1540,7 @@ public Basic.RecoverOk basicRecover() } /** Public API - {@inheritDoc} */ + @Override public Basic.RecoverOk basicRecover(boolean requeue) throws IOException { @@ -1132,6 +1549,7 @@ public Basic.RecoverOk basicRecover(boolean requeue) /** Public API - {@inheritDoc} */ + @Override public Tx.SelectOk txSelect() throws IOException { @@ -1139,6 +1557,7 @@ public Tx.SelectOk txSelect() } /** Public API - {@inheritDoc} */ + @Override public Tx.CommitOk txCommit() throws IOException { @@ -1146,6 +1565,7 @@ public Tx.CommitOk txCommit() } /** Public API - {@inheritDoc} */ + @Override public Tx.RollbackOk txRollback() throws IOException { @@ -1153,45 +1573,61 @@ public Tx.RollbackOk txRollback() } /** Public API - {@inheritDoc} */ + @Override public Confirm.SelectOk confirmSelect() throws IOException { + if (confirmSelectActivated) { + return new Confirm.SelectOk(); + } + if (nextPublishSeqNo == 0) nextPublishSeqNo = 1; - return (Confirm.SelectOk) + Confirm.SelectOk result = (Confirm.SelectOk) exnWrappingRpc(new Confirm.Select(false)).getMethod(); + confirmSelectActivated = true; + return result; } /** Public API - {@inheritDoc} */ - public boolean flowBlocked() { - return _blockContent; - } - - /** Public API - {@inheritDoc} */ + @Override public long getNextPublishSeqNo() { return nextPublishSeqNo; } + @Override public void asyncRpc(Method method) throws IOException { transmit(method); } + @Override public AMQCommand rpc(Method method) throws IOException { return exnWrappingRpc(method); } + @Override + public CompletableFuture asyncCompletableRpc(Method method) throws IOException { + return exnWrappingAsyncRpc(method); + } + @Override public void enqueueRpc(RpcContinuation k) { - synchronized (_channelMutex) { + _channelLock.lock(); + try { super.enqueueRpc(k); dispatcher.setUnlimited(true); + } finally { + _channelLock.unlock(); } } @Override protected void markRpcFinished() { - synchronized (_channelMutex) { + _channelLock.lock(); + try { dispatcher.setUnlimited(false); + } finally { + _channelLock.unlock(); } } @@ -1207,4 +1643,11 @@ private void handleAckNack(long seqNo, boolean multiple, boolean nack) { unconfirmedSet.notifyAll(); } } + + private static void validateQueueNameLength(String queue) { + if(queue.length() > 255) { + throw new IllegalArgumentException("queue name must be no more than 255 characters long"); + } + } + } diff --git a/src/main/java/com/rabbitmq/client/impl/ClientVersion.java b/src/main/java/com/rabbitmq/client/impl/ClientVersion.java new file mode 100644 index 0000000000..b533d91ba7 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/ClientVersion.java @@ -0,0 +1,83 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.util.Properties; + +/** + * Publicly available Client Version information + */ +public class ClientVersion { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClientVersion.class); + + // We store the version property in an unusual way because relocating the package can rewrite the key in the property + // file, which results in spurious warnings being emitted at start-up. + // see https://github.com/rabbitmq/rabbitmq-java-client/issues/436 + private static final char[] VERSION_PROPERTY = new char[] {'c', 'o', 'm', '.', 'r', 'a', 'b', 'b', 'i', 't', 'm', 'q', '.', + 'c', 'l', 'i', 'e', 'n', 't', '.', 'v', 'e', 'r', 's', 'i', 'o', 'n'}; + + public static final String VERSION; + + static { + String version; + try { + version = getVersionFromPropertyFile(); + } catch (Exception e1) { + LOGGER.warn("Couldn't get version from property file", e1); + try { + version = getVersionFromPackage(); + } catch (Exception e2) { + LOGGER.warn("Couldn't get version with Package#getImplementationVersion", e1); + version = getDefaultVersion(); + } + } + VERSION = version; + } + + private static String getVersionFromPropertyFile() throws Exception { + InputStream inputStream = ClientVersion.class.getClassLoader().getResourceAsStream("rabbitmq-amqp-client.properties"); + Properties version = new Properties(); + try { + version.load(inputStream); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + String propertyName = new String(VERSION_PROPERTY); + String versionProperty = version.getProperty(propertyName); + if (versionProperty == null) { + throw new IllegalStateException("Couldn't find version property in property file"); + } + return versionProperty; + } + + private static String getVersionFromPackage() { + if (ClientVersion.class.getPackage().getImplementationVersion() == null) { + throw new IllegalStateException("Couldn't get version with Package#getImplementationVersion"); + } + return ClientVersion.class.getPackage().getImplementationVersion(); + } + + private static String getDefaultVersion() { + return "0.0.0"; + } +} diff --git a/src/com/rabbitmq/client/impl/CommandAssembler.java b/src/main/java/com/rabbitmq/client/impl/CommandAssembler.java similarity index 73% rename from src/com/rabbitmq/client/impl/CommandAssembler.java rename to src/main/java/com/rabbitmq/client/impl/CommandAssembler.java index de74152249..dc051fb318 100644 --- a/src/com/rabbitmq/client/impl/CommandAssembler.java +++ b/src/main/java/com/rabbitmq/client/impl/CommandAssembler.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -22,6 +21,7 @@ import com.rabbitmq.client.AMQP; import com.rabbitmq.client.UnexpectedFrameError; +import static java.lang.String.format; /** * Class responsible for piecing together a command from a series of {@link Frame}s. @@ -53,12 +53,16 @@ private enum CAState { /** No bytes of content body not yet accumulated */ private long remainingBodyBytes; - public CommandAssembler(Method method, AMQContentHeader contentHeader, byte[] body) { + private final int maxBodyLength; + + public CommandAssembler(Method method, AMQContentHeader contentHeader, byte[] body, + int maxBodyLength) { this.method = method; this.contentHeader = contentHeader; - this.bodyN = new ArrayList(2); + this.bodyN = new ArrayList<>(2); this.bodyLength = 0; this.remainingBodyBytes = 0; + this.maxBodyLength = maxBodyLength; appendBodyFragment(body); if (method == null) { this.state = CAState.EXPECTING_METHOD; @@ -89,7 +93,7 @@ private void updateContentBodyState() { } private void consumeMethodFrame(Frame f) throws IOException { - if (f.type == AMQP.FRAME_METHOD) { + if (f.getType() == AMQP.FRAME_METHOD) { this.method = AMQImpl.readMethodFrom(f.getInputStream()); this.state = this.method.hasContent() ? CAState.EXPECTING_CONTENT_HEADER : CAState.COMPLETE; } else { @@ -98,9 +102,18 @@ private void consumeMethodFrame(Frame f) throws IOException { } private void consumeHeaderFrame(Frame f) throws IOException { - if (f.type == AMQP.FRAME_HEADER) { + if (f.getType() == AMQP.FRAME_HEADER) { this.contentHeader = AMQImpl.readContentHeaderFrom(f.getInputStream()); - this.remainingBodyBytes = this.contentHeader.getBodySize(); + long bodySize = this.contentHeader.getBodySize(); + if (bodySize >= this.maxBodyLength) { + throw new IllegalStateException(format( + "Message body is too large (%d), maximum configured size is %d. " + + "See ConnectionFactory#setMaxInboundMessageBodySize " + + "if you need to increase the limit.", + bodySize, this.maxBodyLength + )); + } + this.remainingBodyBytes = bodySize; updateContentBodyState(); } else { throw new UnexpectedFrameError(f, AMQP.FRAME_HEADER); @@ -108,7 +121,7 @@ private void consumeHeaderFrame(Frame f) throws IOException { } private void consumeBodyFrame(Frame f) { - if (f.type == AMQP.FRAME_BODY) { + if (f.getType() == AMQP.FRAME_BODY) { byte[] fragment = f.getPayload(); this.remainingBodyBytes -= fragment.length; updateContentBodyState(); @@ -160,7 +173,7 @@ public synchronized boolean handleFrame(Frame f) throws IOException case EXPECTING_CONTENT_BODY: consumeBodyFrame(f); break; default: - throw new AssertionError("Bad Command State " + this.state); + throw new IllegalStateException("Bad Command State " + this.state); } return isComplete(); } diff --git a/src/main/java/com/rabbitmq/client/impl/CompletableFutureRpcWrapper.java b/src/main/java/com/rabbitmq/client/impl/CompletableFutureRpcWrapper.java new file mode 100644 index 0000000000..a91876e59d --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/CompletableFutureRpcWrapper.java @@ -0,0 +1,51 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.Method; + +import java.util.concurrent.CompletableFuture; + +/** + * + */ +public class CompletableFutureRpcWrapper implements RpcWrapper { + + private final com.rabbitmq.client.Method request; + + private final CompletableFuture completableFuture; + + public CompletableFutureRpcWrapper(Method method, CompletableFuture completableFuture) { + this.request = method; + this.completableFuture = completableFuture; + } + + @Override + public boolean canHandleReply(AMQCommand command) { + return AMQChannel.SimpleBlockingRpcContinuation.isResponseCompatibleWithRequest(request, command.getMethod()); + } + + @Override + public void complete(AMQCommand command) { + completableFuture.complete(command); + } + + @Override + public void shutdown(ShutdownSignalException signal) { + completableFuture.completeExceptionally(signal); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/ConnectionParams.java b/src/main/java/com/rabbitmq/client/impl/ConnectionParams.java new file mode 100644 index 0000000000..9cef972bdd --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/ConnectionParams.java @@ -0,0 +1,309 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.ExceptionHandler; +import com.rabbitmq.client.RecoveryDelayHandler; +import com.rabbitmq.client.RecoveryDelayHandler.DefaultRecoveryDelayHandler; +import com.rabbitmq.client.SaslConfig; +import com.rabbitmq.client.ShutdownSignalException; +import com.rabbitmq.client.TrafficListener; +import com.rabbitmq.client.impl.recovery.RecoveredQueueNameSupplier; +import com.rabbitmq.client.impl.recovery.RetryHandler; +import com.rabbitmq.client.impl.recovery.TopologyRecoveryFilter; + +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.function.Predicate; + +public class ConnectionParams { + private CredentialsProvider credentialsProvider; + private ExecutorService consumerWorkServiceExecutor; + private ScheduledExecutorService heartbeatExecutor; + private ExecutorService shutdownExecutor; + private String virtualHost; + private Map clientProperties; + private int requestedFrameMax; + private int requestedChannelMax; + private int requestedHeartbeat; + private int handshakeTimeout; + private int shutdownTimeout; + private SaslConfig saslConfig; + private long networkRecoveryInterval; + private RecoveryDelayHandler recoveryDelayHandler; + private boolean topologyRecovery; + private ExecutorService topologyRecoveryExecutor; + private int channelRpcTimeout; + private boolean channelShouldCheckRpcResponseType; + private ErrorOnWriteListener errorOnWriteListener; + private int workPoolTimeout = -1; + private TopologyRecoveryFilter topologyRecoveryFilter; + private Predicate connectionRecoveryTriggeringCondition; + private RetryHandler topologyRecoveryRetryHandler; + private RecoveredQueueNameSupplier recoveredQueueNameSupplier; + private ExceptionHandler exceptionHandler; + private ThreadFactory threadFactory; + + private TrafficListener trafficListener; + + private CredentialsRefreshService credentialsRefreshService; + + private int maxInboundMessageBodySize; + + public ConnectionParams() {} + + public CredentialsProvider getCredentialsProvider() { + return credentialsProvider; + } + + public ExecutorService getConsumerWorkServiceExecutor() { + return consumerWorkServiceExecutor; + } + + public String getVirtualHost() { + return virtualHost; + } + + public Map getClientProperties() { + return clientProperties; + } + + public int getRequestedFrameMax() { + return requestedFrameMax; + } + + public int getRequestedChannelMax() { + return requestedChannelMax; + } + + public int getRequestedHeartbeat() { + return requestedHeartbeat; + } + + public int getHandshakeTimeout() { + return handshakeTimeout; + } + + public void setHandshakeTimeout(int timeout) { + this.handshakeTimeout = timeout; + } + + public int getShutdownTimeout() { + return shutdownTimeout; + } + + public SaslConfig getSaslConfig() { + return saslConfig; + } + + public ExceptionHandler getExceptionHandler() { + return exceptionHandler; + } + + public long getNetworkRecoveryInterval() { + return networkRecoveryInterval; + } + + /** + * Get the recovery delay handler. + * @return recovery delay handler or if none was set a {@link DefaultRecoveryDelayHandler} will be returned with a delay of {@link #getNetworkRecoveryInterval()}. + */ + public RecoveryDelayHandler getRecoveryDelayHandler() { + return recoveryDelayHandler == null ? new DefaultRecoveryDelayHandler(networkRecoveryInterval) : recoveryDelayHandler; + } + + public boolean isTopologyRecoveryEnabled() { + return topologyRecovery; + } + + /** + * Get the topology recovery executor. If null, the main connection thread should be used. + * @return executor. May be null. + */ + public ExecutorService getTopologyRecoveryExecutor() { + return topologyRecoveryExecutor; + } + + public ThreadFactory getThreadFactory() { + return threadFactory; + } + + public int getChannelRpcTimeout() { + return channelRpcTimeout; + } + + public boolean channelShouldCheckRpcResponseType() { + return channelShouldCheckRpcResponseType; + } + + public void setCredentialsProvider(CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + } + + public void setConsumerWorkServiceExecutor(ExecutorService consumerWorkServiceExecutor) { + this.consumerWorkServiceExecutor = consumerWorkServiceExecutor; + } + + public void setVirtualHost(String virtualHost) { + this.virtualHost = virtualHost; + } + + public void setClientProperties(Map clientProperties) { + this.clientProperties = clientProperties; + } + + public void setRequestedFrameMax(int requestedFrameMax) { + this.requestedFrameMax = requestedFrameMax; + } + + public void setRequestedChannelMax(int requestedChannelMax) { + this.requestedChannelMax = requestedChannelMax; + } + + public void setRequestedHeartbeat(int requestedHeartbeat) { + this.requestedHeartbeat = requestedHeartbeat; + } + + public void setShutdownTimeout(int shutdownTimeout) { + this.shutdownTimeout = shutdownTimeout; + } + + public void setSaslConfig(SaslConfig saslConfig) { + this.saslConfig = saslConfig; + } + + public void setNetworkRecoveryInterval(long networkRecoveryInterval) { + this.networkRecoveryInterval = networkRecoveryInterval; + } + + public void setRecoveryDelayHandler(final RecoveryDelayHandler recoveryDelayHandler) { + this.recoveryDelayHandler = recoveryDelayHandler; + } + + public void setTopologyRecovery(boolean topologyRecovery) { + this.topologyRecovery = topologyRecovery; + } + + public void setTopologyRecoveryExecutor(final ExecutorService topologyRecoveryExecutor) { + this.topologyRecoveryExecutor = topologyRecoveryExecutor; + } + + public void setExceptionHandler(ExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + + public void setThreadFactory(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + } + + public ExecutorService getShutdownExecutor() { + return shutdownExecutor; + } + + public void setShutdownExecutor(ExecutorService shutdownExecutor) { + this.shutdownExecutor = shutdownExecutor; + } + + public ScheduledExecutorService getHeartbeatExecutor() { + return heartbeatExecutor; + } + + public void setHeartbeatExecutor(ScheduledExecutorService heartbeatExecutor) { + this.heartbeatExecutor = heartbeatExecutor; + } + + public void setChannelRpcTimeout(int channelRpcTimeout) { + this.channelRpcTimeout = channelRpcTimeout; + } + + public void setChannelShouldCheckRpcResponseType(boolean channelShouldCheckRpcResponseType) { + this.channelShouldCheckRpcResponseType = channelShouldCheckRpcResponseType; + } + + public void setErrorOnWriteListener(ErrorOnWriteListener errorOnWriteListener) { + this.errorOnWriteListener = errorOnWriteListener; + } + + public ErrorOnWriteListener getErrorOnWriteListener() { + return errorOnWriteListener; + } + + public void setWorkPoolTimeout(int workPoolTimeout) { + this.workPoolTimeout = workPoolTimeout; + } + + public int getWorkPoolTimeout() { + return workPoolTimeout; + } + + public void setTopologyRecoveryFilter(TopologyRecoveryFilter topologyRecoveryFilter) { + this.topologyRecoveryFilter = topologyRecoveryFilter; + } + + public TopologyRecoveryFilter getTopologyRecoveryFilter() { + return topologyRecoveryFilter; + } + + public void setConnectionRecoveryTriggeringCondition(Predicate connectionRecoveryTriggeringCondition) { + this.connectionRecoveryTriggeringCondition = connectionRecoveryTriggeringCondition; + } + + public Predicate getConnectionRecoveryTriggeringCondition() { + return connectionRecoveryTriggeringCondition; + } + + public void setTopologyRecoveryRetryHandler(RetryHandler topologyRecoveryRetryHandler) { + this.topologyRecoveryRetryHandler = topologyRecoveryRetryHandler; + } + + public RetryHandler getTopologyRecoveryRetryHandler() { + return topologyRecoveryRetryHandler; + } + + public void setRecoveredQueueNameSupplier(RecoveredQueueNameSupplier recoveredQueueNameSupplier) { + this.recoveredQueueNameSupplier = recoveredQueueNameSupplier; + } + + public RecoveredQueueNameSupplier getRecoveredQueueNameSupplier() { + return recoveredQueueNameSupplier; + } + + public void setTrafficListener(TrafficListener trafficListener) { + this.trafficListener = trafficListener; + } + + public TrafficListener getTrafficListener() { + return trafficListener; + } + + public void setCredentialsRefreshService(CredentialsRefreshService credentialsRefreshService) { + this.credentialsRefreshService = credentialsRefreshService; + } + + public CredentialsRefreshService getCredentialsRefreshService() { + return credentialsRefreshService; + } + + public int getMaxInboundMessageBodySize() { + return maxInboundMessageBodySize; + } + + public void setMaxInboundMessageBodySize(int maxInboundMessageBodySize) { + this.maxInboundMessageBodySize = maxInboundMessageBodySize; + } +} diff --git a/src/com/rabbitmq/client/impl/ConsumerDispatcher.java b/src/main/java/com/rabbitmq/client/impl/ConsumerDispatcher.java similarity index 88% rename from src/com/rabbitmq/client/impl/ConsumerDispatcher.java rename to src/main/java/com/rabbitmq/client/impl/ConsumerDispatcher.java index 64c7f7eb79..b4ed18e172 100644 --- a/src/com/rabbitmq/client/impl/ConsumerDispatcher.java +++ b/src/main/java/com/rabbitmq/client/impl/ConsumerDispatcher.java @@ -1,17 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -70,6 +70,7 @@ public void handleConsumeOk(final Consumer delegate, final String consumerTag) { executeUnlessShuttingDown( new Runnable() { + @Override public void run() { try { delegate.handleConsumeOk(consumerTag); @@ -89,6 +90,7 @@ public void handleCancelOk(final Consumer delegate, final String consumerTag) { executeUnlessShuttingDown( new Runnable() { + @Override public void run() { try { delegate.handleCancelOk(consumerTag); @@ -107,7 +109,8 @@ public void run() { public void handleCancel(final Consumer delegate, final String consumerTag) { executeUnlessShuttingDown( new Runnable() { - public void run() { + @Override + public void run() { try { delegate.handleCancel(consumerTag); } catch (Throwable ex) { @@ -126,6 +129,7 @@ public void run() { public void handleRecoverOk(final Consumer delegate, final String consumerTag) { executeUnlessShuttingDown( new Runnable() { + @Override public void run() { delegate.handleRecoverOk(consumerTag); } @@ -139,6 +143,7 @@ public void handleDelivery(final Consumer delegate, final byte[] body) throws IOException { executeUnlessShuttingDown( new Runnable() { + @Override public void run() { try { delegate.handleDelivery(consumerTag, @@ -166,6 +171,7 @@ public CountDownLatch handleShutdownSignal(final Map consumers this.shutdownConsumersDriven = true; // Execute shutdown processing even if there are no consumers. execute(new Runnable() { + @Override public void run() { ConsumerDispatcher.this.notifyConsumersOfShutdown(consumers, signal); ConsumerDispatcher.this.shutdown(signal); diff --git a/src/com/rabbitmq/client/impl/ConsumerWorkService.java b/src/main/java/com/rabbitmq/client/impl/ConsumerWorkService.java similarity index 62% rename from src/com/rabbitmq/client/impl/ConsumerWorkService.java rename to src/main/java/com/rabbitmq/client/impl/ConsumerWorkService.java index 83dc8fd25b..6895e80d9d 100644 --- a/src/com/rabbitmq/client/impl/ConsumerWorkService.java +++ b/src/main/java/com/rabbitmq/client/impl/ConsumerWorkService.java @@ -1,17 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -22,23 +22,34 @@ import java.util.concurrent.ThreadFactory; import com.rabbitmq.client.Channel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; final public class ConsumerWorkService { - private static final int MAX_RUNNABLE_BLOCK_SIZE = 16; - private static final int DEFAULT_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 2; + private static final Logger LOGGER = LoggerFactory.getLogger(ConsumerWorkService.class); + private static final int MAX_RUNNABLE_BLOCK_SIZE = 256; + private static final int DEFAULT_NUM_THREADS = Math.max(1, Utils.availableProcessors()); private final ExecutorService executor; private final boolean privateExecutor; private final WorkPool workPool; private final int shutdownTimeout; - public ConsumerWorkService(ExecutorService executor, ThreadFactory threadFactory, int shutdownTimeout) { + public ConsumerWorkService(ExecutorService executor, ThreadFactory threadFactory, int queueingTimeout, int shutdownTimeout) { this.privateExecutor = (executor == null); - this.executor = (executor == null) ? Executors.newFixedThreadPool(DEFAULT_NUM_THREADS, threadFactory) - : executor; - this.workPool = new WorkPool(); + if (executor == null) { + LOGGER.debug("Creating executor service with {} thread(s) for consumer work service", DEFAULT_NUM_THREADS); + this.executor = Executors.newFixedThreadPool(DEFAULT_NUM_THREADS, threadFactory); + } else { + this.executor = executor; + } + this.workPool = new WorkPool<>(queueingTimeout); this.shutdownTimeout = shutdownTimeout; } + public ConsumerWorkService(ExecutorService executor, ThreadFactory threadFactory, int shutdownTimeout) { + this(executor, threadFactory, -1, shutdownTimeout); + } + public int getShutdownTimeout() { return shutdownTimeout; } @@ -88,6 +99,7 @@ public boolean usesPrivateExecutor() { private final class WorkPoolRunnable implements Runnable { + @Override public void run() { int size = MAX_RUNNABLE_BLOCK_SIZE; List block = new ArrayList(size); diff --git a/src/com/rabbitmq/client/impl/ContentHeaderPropertyReader.java b/src/main/java/com/rabbitmq/client/impl/ContentHeaderPropertyReader.java similarity index 78% rename from src/com/rabbitmq/client/impl/ContentHeaderPropertyReader.java rename to src/main/java/com/rabbitmq/client/impl/ContentHeaderPropertyReader.java index c3f69baa26..322dc39ccb 100644 --- a/src/com/rabbitmq/client/impl/ContentHeaderPropertyReader.java +++ b/src/main/java/com/rabbitmq/client/impl/ContentHeaderPropertyReader.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -35,10 +34,10 @@ public class ContentHeaderPropertyReader { private final ValueReader in; /** Current field flag word */ - public int flagWord; + private int flagWord; /** Current flag position counter */ - public int bitCount; + private int bitCount; /** * Protected API - Constructs a reader from the given input stream diff --git a/src/com/rabbitmq/client/impl/ContentHeaderPropertyWriter.java b/src/main/java/com/rabbitmq/client/impl/ContentHeaderPropertyWriter.java similarity index 74% rename from src/com/rabbitmq/client/impl/ContentHeaderPropertyWriter.java rename to src/main/java/com/rabbitmq/client/impl/ContentHeaderPropertyWriter.java index e869b48dfb..36ef3dd9f2 100644 --- a/src/com/rabbitmq/client/impl/ContentHeaderPropertyWriter.java +++ b/src/main/java/com/rabbitmq/client/impl/ContentHeaderPropertyWriter.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -34,10 +33,10 @@ public class ContentHeaderPropertyWriter { private final ValueWriter out; /** Current flags word being accumulated */ - public int flagWord; + private int flagWord; /** Position within current flags word */ - public int bitCount; + private int bitCount; /** * Constructs a fresh ContentHeaderPropertyWriter. diff --git a/src/main/java/com/rabbitmq/client/impl/CredentialsProvider.java b/src/main/java/com/rabbitmq/client/impl/CredentialsProvider.java new file mode 100644 index 0000000000..e39ffc56f7 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/CredentialsProvider.java @@ -0,0 +1,66 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import java.time.Duration; + +/** + * Provider interface for establishing credentials for connecting to the broker. Especially useful + * for situations where credentials might expire or change before a recovery takes place or where it is + * convenient to plug in an outside custom implementation. + * + * @see CredentialsRefreshService + * @since 5.2.0 + */ +public interface CredentialsProvider { + + /** + * Username to use for authentication + * + * @return username + */ + String getUsername(); + + /** + * Password/secret/token to use for authentication + * + * @return password/secret/token + */ + String getPassword(); + + /** + * The time before the credentials expire, if they do expire. + *

+ * If credentials do not expire, must return null. Default + * behavior is to return null, assuming credentials never + * expire. + * + * @return time before expiration + */ + default Duration getTimeBeforeExpiration() { + return null; + } + + /** + * Instructs the provider to refresh or renew credentials. + *

+ * Default behavior is no-op. + */ + default void refresh() { + // no need to refresh anything by default + } + +} diff --git a/src/main/java/com/rabbitmq/client/impl/CredentialsRefreshService.java b/src/main/java/com/rabbitmq/client/impl/CredentialsRefreshService.java new file mode 100644 index 0000000000..0cb94e22d7 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/CredentialsRefreshService.java @@ -0,0 +1,77 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import java.time.Duration; +import java.util.concurrent.Callable; + +/** + * Provider interface to refresh credentials when appropriate + * and perform an operation once the credentials have been + * renewed. In the context of RabbitMQ, the operation consists + * in calling the update.secret AMQP extension + * to provide new valid credentials before the current ones + * expire. + *

+ * New connections are registered and implementations must perform + * credentials renewal when appropriate. Implementations + * must call a registered callback once credentials are renewed. + * + * @see CredentialsProvider + * @see DefaultCredentialsRefreshService + */ +public interface CredentialsRefreshService { + + /** + * Register a new entity that needs credentials renewal. + *

+ * The registered callback must return true if the action was + * performed correctly, throw an exception if something goes wrong, + * and return false if it became stale and wants to be unregistered. + *

+ * Implementations are free to automatically unregister an entity whose + * callback has failed a given number of times. + * + * @param credentialsProvider the credentials provider + * @param refreshAction the action to perform after credentials renewal + * @return a tracking ID for the registration + */ + String register(CredentialsProvider credentialsProvider, Callable refreshAction); + + /** + * Unregister the entity with the given registration ID. + *

+ * Its state is cleaned up and its registered callback will not be + * called again. + * + * @param credentialsProvider the credentials provider + * @param registrationId the registration ID + */ + void unregister(CredentialsProvider credentialsProvider, String registrationId); + + /** + * Provide a hint about whether credentials should be renewed now or not before attempting to connect. + *

+ * This can avoid a connection to use almost expired credentials if this connection + * is created just before credentials are refreshed in the background, but does not + * benefit from the refresh. + * + * @param timeBeforeExpiration + * @return true if credentials should be renewed, false otherwise + */ + boolean isApproachingExpiration(Duration timeBeforeExpiration); + +} diff --git a/src/main/java/com/rabbitmq/client/impl/DefaultCredentialsProvider.java b/src/main/java/com/rabbitmq/client/impl/DefaultCredentialsProvider.java new file mode 100644 index 0000000000..7704f2ddac --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/DefaultCredentialsProvider.java @@ -0,0 +1,44 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +/** + * Default implementation of a CredentialsProvider which simply holds a static + * username and password. + * + * @since 4.5.0 + */ +public class DefaultCredentialsProvider implements CredentialsProvider { + + private final String username; + private final String password; + + public DefaultCredentialsProvider(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public String getPassword() { + return password; + } + +} diff --git a/src/main/java/com/rabbitmq/client/impl/DefaultCredentialsRefreshService.java b/src/main/java/com/rabbitmq/client/impl/DefaultCredentialsRefreshService.java new file mode 100644 index 0000000000..7e4c4224fd --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/DefaultCredentialsRefreshService.java @@ -0,0 +1,443 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Scheduling-based implementation of {@link CredentialsRefreshService}. + *

+ * This implementation keeps track of entities (typically AMQP connections) that need + * to renew credentials. Token renewal is scheduled based on token expiration, using + * a Function<Duration, Long> refreshDelayStrategy. Once credentials + * for a {@link CredentialsProvider} have been renewed, the callback registered + * by each entity/connection is performed. This callback typically propagates + * the new credentials in the entity state, e.g. sending the new password to the + * broker for AMQP connections. + *

+ * Instances are preferably created with {@link DefaultCredentialsRefreshServiceBuilder}. + */ +public class DefaultCredentialsRefreshService implements CredentialsRefreshService { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCredentialsRefreshService.class); + + /** + * Scheduler used to schedule credentials refresh. + *

+ * Default is a single-threaded scheduler, which should be enough for most scenarios, assuming + * that credentials expire after a few minutes or hours. This default scheduler + * is automatically disposed of when the {@link DefaultCredentialsRefreshService} is closed. + *

+ * If an external scheduler is passed in, it is the developer's responsibility to + * close it. + */ + private final ScheduledExecutorService scheduler; + + private final ConcurrentMap credentialsProviderStates = new ConcurrentHashMap<>(); + + private final boolean privateScheduler; + + /** + * Strategy to schedule credentials refresh after credentials retrieval. + *

+ * Typical strategies schedule refresh after a ratio of the time before expiration + * (e.g. 80 % of the time before expiration) or after a fixed time before + * expiration (e.g. 20 seconds before credentials expire). + * + * @see #ratioRefreshDelayStrategy(double) + * @see #fixedDelayBeforeExpirationRefreshDelayStrategy(Duration) + */ + private final Function refreshDelayStrategy; + + /** + * Strategy to provide a hint about whether credentials should be renewed now or not before attempting to connect. + *

+ * This can avoid a connection to use almost expired credentials if this connection + * is created just before credentials are refreshed in the background, but does not + * benefit from the refresh. + *

+ * Note setting such a strategy may require knowledge of the credentials validity and must be consistent + * with the {@link #refreshDelayStrategy} chosen. For example, for a validity of 60 minutes and + * a {@link #refreshDelayStrategy} that instructs to refresh 10 minutes before credentials expire, this + * strategy could hint that credentials that expire in 11 minutes or less (1 minute before a refresh is actually + * scheduled) should be refreshed, which would trigger an early refresh. + *

+ * The default strategy always return false. + */ + private final Function approachingExpirationStrategy; + + /** + * Constructor. Consider using {@link DefaultCredentialsRefreshServiceBuilder} to create instances. + * + * @param scheduler + * @param refreshDelayStrategy + * @param approachingExpirationStrategy + */ + public DefaultCredentialsRefreshService(ScheduledExecutorService scheduler, Function refreshDelayStrategy, Function approachingExpirationStrategy) { + if (refreshDelayStrategy == null) { + throw new IllegalArgumentException("Refresh delay strategy can not be null"); + } + this.refreshDelayStrategy = refreshDelayStrategy; + this.approachingExpirationStrategy = approachingExpirationStrategy == null ? duration -> false : approachingExpirationStrategy; + if (scheduler == null) { + this.scheduler = Executors.newScheduledThreadPool(1); + privateScheduler = true; + } else { + this.scheduler = scheduler; + privateScheduler = false; + } + } + + /** + * Delay before refresh is a ratio of the time before expiration. + *

+ * E.g. if time before expiration is 60 minutes and specified ratio is 0.8, refresh will + * be scheduled in 60 x 0.8 = 48 minutes. + * + * @param ratio + * @return the delay before refreshing + */ + public static Function ratioRefreshDelayStrategy(double ratio) { + return new RatioRefreshDelayStrategy(ratio); + } + + /** + * Delay before refresh is time before expiration - specified duration. + *

+ * E.g. if time before expiration is 60 minutes and specified duration is 10 minutes, refresh will + * be scheduled in 60 - 10 = 50 minutes. + * + * @param duration + * @return the delay before refreshing + */ + public static Function fixedDelayBeforeExpirationRefreshDelayStrategy(Duration duration) { + return new FixedDelayBeforeExpirationRefreshDelayStrategy(duration); + } + + /** + * Advise to refresh credentials if TTL <= limit. + * + * @param limitBeforeExpiration + * @return true if credentials should be refreshed, false otherwise + */ + public static Function fixedTimeApproachingExpirationStrategy(Duration limitBeforeExpiration) { + return new FixedTimeApproachingExpirationStrategy(limitBeforeExpiration.toMillis()); + } + + private static Runnable refresh(ScheduledExecutorService scheduler, CredentialsProviderState credentialsProviderState, + Function refreshDelayStrategy) { + return () -> { + LOGGER.debug("Refreshing token"); + credentialsProviderState.refresh(); + + Duration timeBeforeExpiration = credentialsProviderState.credentialsProvider.getTimeBeforeExpiration(); + Duration newDelay = refreshDelayStrategy.apply(timeBeforeExpiration); + + LOGGER.debug("Scheduling refresh in {} seconds", newDelay.getSeconds()); + + ScheduledFuture scheduledFuture = scheduler.schedule( + refresh(scheduler, credentialsProviderState, refreshDelayStrategy), + newDelay.getSeconds(), + TimeUnit.SECONDS + ); + credentialsProviderState.refreshTask.set(scheduledFuture); + }; + } + + @Override + public String register(CredentialsProvider credentialsProvider, Callable refreshAction) { + String registrationId = UUID.randomUUID().toString(); + LOGGER.debug("New registration {}", registrationId); + + Registration registration = new Registration(registrationId, refreshAction); + CredentialsProviderState credentialsProviderState = credentialsProviderStates.computeIfAbsent( + credentialsProvider, + credentialsProviderKey -> new CredentialsProviderState(credentialsProviderKey) + ); + + credentialsProviderState.add(registration); + + credentialsProviderState.maybeSetRefreshTask(() -> { + Duration delay = refreshDelayStrategy.apply(credentialsProvider.getTimeBeforeExpiration()); + LOGGER.debug("Scheduling refresh in {} seconds", delay.getSeconds()); + return scheduler.schedule( + refresh(scheduler, credentialsProviderState, refreshDelayStrategy), + delay.getSeconds(), + TimeUnit.SECONDS + ); + }); + + return registrationId; + } + + @Override + public void unregister(CredentialsProvider credentialsProvider, String registrationId) { + CredentialsProviderState credentialsProviderState = this.credentialsProviderStates.get(credentialsProvider); + if (credentialsProviderState != null) { + credentialsProviderState.unregister(registrationId); + } + } + + @Override + public boolean isApproachingExpiration(Duration timeBeforeExpiration) { + return this.approachingExpirationStrategy.apply(timeBeforeExpiration); + } + + public void close() { + if (privateScheduler) { + scheduler.shutdownNow(); + } + } + + private static class FixedTimeApproachingExpirationStrategy implements Function { + + private final long limitBeforeExpiration; + + private FixedTimeApproachingExpirationStrategy(long limitBeforeExpiration) { + this.limitBeforeExpiration = limitBeforeExpiration; + } + + @Override + public Boolean apply(Duration timeBeforeExpiration) { + return timeBeforeExpiration.toMillis() <= limitBeforeExpiration; + } + } + + private static class FixedDelayBeforeExpirationRefreshDelayStrategy implements Function { + + private final Duration delay; + + private FixedDelayBeforeExpirationRefreshDelayStrategy(Duration delay) { + this.delay = delay; + } + + @Override + public Duration apply(Duration timeBeforeExpiration) { + Duration refreshTimeBeforeExpiration = timeBeforeExpiration.minus(delay); + if (refreshTimeBeforeExpiration.isNegative()) { + return timeBeforeExpiration; + } else { + return refreshTimeBeforeExpiration; + } + } + } + + private static class RatioRefreshDelayStrategy implements Function { + + private final double ratio; + + private RatioRefreshDelayStrategy(double ratio) { + if (ratio < 0 || ratio > 1) { + throw new IllegalArgumentException("Ratio should be > 0 and <= 1: " + ratio); + } + this.ratio = ratio; + } + + @Override + public Duration apply(Duration duration) { + return Duration.ofSeconds((long) ((double) duration.getSeconds() * ratio)); + } + } + + static class Registration { + + private final Callable refreshAction; + + private final AtomicInteger errorHistory = new AtomicInteger(0); + + private final String id; + + Registration(String id, Callable refreshAction) { + this.refreshAction = refreshAction; + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Registration that = (Registration) o; + + return id.equals(that.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + } + + /** + * State and refresh behavior for a {@link CredentialsProvider} and + * its registered entities. + */ + static class CredentialsProviderState { + + private final CredentialsProvider credentialsProvider; + + private final Map registrations = new ConcurrentHashMap<>(); + + private final AtomicReference> refreshTask = new AtomicReference<>(); + + private final AtomicBoolean refreshTaskSet = new AtomicBoolean(false); + + CredentialsProviderState(CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + } + + void add(Registration registration) { + this.registrations.put(registration.id, registration); + } + + void maybeSetRefreshTask(Supplier> scheduledFutureSupplier) { + if (refreshTaskSet.compareAndSet(false, true)) { + refreshTask.set(scheduledFutureSupplier.get()); + } + } + + void refresh() { + if (Thread.currentThread().isInterrupted()) { + return; + } + + int attemptCount = 0; + boolean refreshSucceeded = false; + while (attemptCount < 3) { + LOGGER.debug("Refreshing token for credentials provider {}", credentialsProvider); + try { + this.credentialsProvider.refresh(); + LOGGER.debug("Token refreshed for credentials provider {}", credentialsProvider); + refreshSucceeded = true; + break; + } catch (Exception e) { + LOGGER.warn("Error while trying to refresh token: {}", e.getMessage()); + } + attemptCount++; + try { + Thread.sleep(1000L); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + + if (!refreshSucceeded) { + LOGGER.warn("Token refresh failed after retry, aborting callbacks"); + return; + } + + Iterator iterator = registrations.values().iterator(); + while (iterator.hasNext() && !Thread.currentThread().isInterrupted()) { + Registration registration = iterator.next(); + // FIXME set a timeout on the call? (needs a separate thread) + try { + boolean refreshed = registration.refreshAction.call(); + if (!refreshed) { + LOGGER.debug("Registration did not refresh token"); + iterator.remove(); + } + registration.errorHistory.set(0); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Exception e) { + LOGGER.warn("Error while trying to refresh a connection token", e); + registration.errorHistory.incrementAndGet(); + if (registration.errorHistory.get() >= 5) { + registrations.remove(registration.id); + } + } + } + } + + void unregister(String registrationId) { + this.registrations.remove(registrationId); + } + } + + /** + * Builder to create instances of {@link DefaultCredentialsRefreshServiceBuilder}. + */ + public static class DefaultCredentialsRefreshServiceBuilder { + + + private ScheduledExecutorService scheduler; + + private Function refreshDelayStrategy = ratioRefreshDelayStrategy(0.8); + + private Function approachingExpirationStrategy = ttl -> false; + + public DefaultCredentialsRefreshServiceBuilder scheduler(ScheduledThreadPoolExecutor scheduler) { + this.scheduler = scheduler; + return this; + } + + /** + * Set the strategy to schedule credentials refresh after credentials retrieval. + *

+ * Default is a 80 % ratio-based strategy (refresh is scheduled after 80 % of the time + * before expiration, e.g. 48 minutes for a token with a validity of 60 minutes, that + * is refresh will be scheduled 12 minutes before the token actually expires). + * + * @param refreshDelayStrategy + * @return this builder instance + * @see DefaultCredentialsRefreshService#refreshDelayStrategy + * @see DefaultCredentialsRefreshService#ratioRefreshDelayStrategy(double) + */ + public DefaultCredentialsRefreshServiceBuilder refreshDelayStrategy(Function refreshDelayStrategy) { + this.refreshDelayStrategy = refreshDelayStrategy; + return this; + } + + /** + * Set the strategy to trigger an early refresh before attempting to connect. + *

+ * Default is to never advise to refresh before connecting. + * + * @param approachingExpirationStrategy + * @return this builder instances + * @see DefaultCredentialsRefreshService#approachingExpirationStrategy + * @see CredentialsRefreshService#isApproachingExpiration(Duration) + */ + public DefaultCredentialsRefreshServiceBuilder approachingExpirationStrategy(Function approachingExpirationStrategy) { + this.approachingExpirationStrategy = approachingExpirationStrategy; + return this; + } + + /** + * Create the {@link DefaultCredentialsRefreshService} instance. + * + * @return + */ + public DefaultCredentialsRefreshService build() { + return new DefaultCredentialsRefreshService(scheduler, refreshDelayStrategy, approachingExpirationStrategy); + } + + } + +} diff --git a/src/main/java/com/rabbitmq/client/impl/DefaultExceptionHandler.java b/src/main/java/com/rabbitmq/client/impl/DefaultExceptionHandler.java new file mode 100644 index 0000000000..bc31bb7575 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/DefaultExceptionHandler.java @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.ExceptionHandler; + +/** + * Default implementation of {@link com.rabbitmq.client.ExceptionHandler} + * used by {@link AMQConnection}. + */ +public class DefaultExceptionHandler extends StrictExceptionHandler implements ExceptionHandler { +} diff --git a/src/main/java/com/rabbitmq/client/impl/Environment.java b/src/main/java/com/rabbitmq/client/impl/Environment.java new file mode 100644 index 0000000000..4475ae2eb0 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/Environment.java @@ -0,0 +1,51 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import java.util.concurrent.ThreadFactory; + +/** + * Infers information about the execution environment, e.g. + * security permissions. + * Package-protected API. + */ +public class Environment { + + /** + * This method is deprecated and subject to removal in the next major release. + * + * There is no replacement for this method, as it used to use the + * {@link SecurityManager}, which is itself deprecated and subject to removal. + * @deprecated + * @return always returns true + */ + @Deprecated + public static boolean isAllowedToModifyThreads() { + return true; + } + + public static Thread newThread(ThreadFactory factory, Runnable runnable, String name) { + Thread t = factory.newThread(runnable); + t.setName(name); + return t; + } + + public static Thread newThread(ThreadFactory factory, Runnable runnable, String name, boolean isDaemon) { + Thread t = newThread(factory, runnable, name); + t.setDaemon(isDaemon); + return t; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/ErrorOnWriteListener.java b/src/main/java/com/rabbitmq/client/impl/ErrorOnWriteListener.java new file mode 100644 index 0000000000..0e9246f18a --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/ErrorOnWriteListener.java @@ -0,0 +1,37 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.Connection; + +import java.io.IOException; + +/** + * Listener called when a connection gets an IO error trying to write on the socket. + * This can be used to trigger connection recovery. + * + * @since 4.5.0 + */ +public interface ErrorOnWriteListener { + + /** + * Called when writing to the socket failed + * @param connection the owning connection instance + * @param exception the thrown exception + */ + void handle(Connection connection, IOException exception) throws IOException; + +} diff --git a/src/main/java/com/rabbitmq/client/impl/ExternalMechanism.java b/src/main/java/com/rabbitmq/client/impl/ExternalMechanism.java new file mode 100644 index 0000000000..f6c1a41397 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/ExternalMechanism.java @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.LongString; +import com.rabbitmq.client.SaslMechanism; + +/** + * The EXTERNAL auth mechanism + */ +public class ExternalMechanism implements SaslMechanism { + @Override + public String getName() { + return "EXTERNAL"; + } + + @Override + public LongString handleChallenge(LongString challenge, String username, String password) { + return LongStringHelper.asLongString(""); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/ForgivingExceptionHandler.java b/src/main/java/com/rabbitmq/client/impl/ForgivingExceptionHandler.java new file mode 100644 index 0000000000..82b7e2054a --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/ForgivingExceptionHandler.java @@ -0,0 +1,133 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.*; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.ConnectException; + +/** + * An implementation of {@link com.rabbitmq.client.ExceptionHandler} that does not + * close channels on unhandled consumer and listener exception. + * + * Used by {@link AMQConnection}. + * + * @see ExceptionHandler + * @see com.rabbitmq.client.ConnectionFactory#setExceptionHandler(com.rabbitmq.client.ExceptionHandler) + */ +public class ForgivingExceptionHandler implements ExceptionHandler { + @Override + public void handleUnexpectedConnectionDriverException(Connection conn, Throwable exception) { + log("An unexpected connection driver error occurred", exception); + } + + @Override + public void handleReturnListenerException(Channel channel, Throwable exception) { + handleChannelKiller(channel, exception, "ReturnListener.handleReturn"); + } + + @Override + public void handleConfirmListenerException(Channel channel, Throwable exception) { + handleChannelKiller(channel, exception, "ConfirmListener.handle{N,A}ck"); + } + + @Override + public void handleBlockedListenerException(Connection connection, Throwable exception) { + handleConnectionKiller(connection, exception, "BlockedListener"); + } + + @Override + public void handleConsumerException(Channel channel, Throwable exception, + Consumer consumer, String consumerTag, + String methodName) + { + handleChannelKiller(channel, exception, "Consumer " + consumer + + " (" + consumerTag + ")" + + " method " + methodName + + " for channel " + channel); + } + + /** + * @since 3.3.0 + */ + @Override + public void handleConnectionRecoveryException(Connection conn, Throwable exception) { + // ignore java.net.ConnectException as those are + // expected during recovery and will only produce noisy + // traces + if (exception instanceof ConnectException) { + // no-op + } else { + log("Caught an exception during connection recovery!", exception); + } + } + + /** + * @since 3.3.0 + */ + @Override + public void handleChannelRecoveryException(Channel ch, Throwable exception) { + log("Caught an exception when recovering channel " + ch.getChannelNumber(), exception); + } + + /** + * @since 3.3.0 + */ + @Override + public void handleTopologyRecoveryException(Connection conn, Channel ch, TopologyRecoveryException exception) { + log("Caught an exception when recovering topology " + exception.getMessage(), exception); + } + + protected void handleChannelKiller(Channel channel, Throwable exception, String what) { + log(what + "threw an exception for channel "+channel, exception); + } + + protected void handleConnectionKiller(Connection connection, Throwable exception, String what) { + log(what + " threw an exception for connection " + connection, exception); + try { + connection.close(AMQP.REPLY_SUCCESS, "Closed due to exception from " + what); + } catch (AlreadyClosedException ace) { + // noop + } catch (IOException ioe) { + log("Failure during close of connection " + connection + " after " + exception, ioe); + connection.abort(AMQP.INTERNAL_ERROR, "Internal error closing connection for " + what); + } + } + + protected void log(String message, Throwable e) { + if(isSocketClosedOrConnectionReset(e)) { + // we don't want to get too dramatic about those + LoggerFactory.getLogger(ForgivingExceptionHandler.class).warn( + message + " (Exception message: "+e.getMessage() + ")" + ); + } else { + LoggerFactory.getLogger(ForgivingExceptionHandler.class).error( + message, e + ); + } + } + + + private static boolean isSocketClosedOrConnectionReset(Throwable e) { + return e instanceof IOException && + ("Connection reset".equals(e.getMessage()) || "Socket closed".equals(e.getMessage()) || + "Connection reset by peer".equals(e.getMessage()) + ); + } + +} diff --git a/src/com/rabbitmq/client/impl/Frame.java b/src/main/java/com/rabbitmq/client/impl/Frame.java similarity index 84% rename from src/com/rabbitmq/client/impl/Frame.java rename to src/main/java/com/rabbitmq/client/impl/Frame.java index 5b3fc491ef..858400b5f6 100644 --- a/src/com/rabbitmq/client/impl/Frame.java +++ b/src/main/java/com/rabbitmq/client/impl/Frame.java @@ -1,48 +1,41 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.LongString; +import com.rabbitmq.client.MalformedFrameException; + +import java.io.*; import java.math.BigDecimal; import java.net.SocketTimeoutException; -import java.sql.Timestamp; import java.util.Date; -import java.util.Map; import java.util.List; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.LongString; -import com.rabbitmq.client.MalformedFrameException; +import java.util.Map; +import static java.lang.String.format; /** * Represents an AMQP wire-protocol frame, with frame type, channel number, and payload bytes. - * TODO: make state private */ public class Frame { /** Frame type code */ - public final int type; + private final int type; /** Frame channel number, 0-65535 */ - public final int channel; + private final int channel; /** Frame payload bytes (for inbound frames) */ private final byte[] payload; @@ -50,6 +43,8 @@ public class Frame { /** Frame payload (for outbound frames) */ private final ByteArrayOutputStream accumulator; + private static final int NON_BODY_SIZE = 1 /* type */ + 2 /* channel */ + 4 /* payload size */ + 1 /* end character */; + /** * Constructs a frame for output with a type and a channel number and a * fresh accumulator waiting for payload. @@ -87,7 +82,7 @@ public static Frame fromBodyFragment(int channelNumber, byte[] body, int offset, * * @return a new Frame if we read a frame successfully, otherwise null */ - public static Frame readFrom(DataInputStream is) throws IOException { + public static Frame readFrom(DataInputStream is, int maxPayloadSize) throws IOException { int type; int channel; @@ -113,6 +108,14 @@ public static Frame readFrom(DataInputStream is) throws IOException { channel = is.readUnsignedShort(); int payloadSize = is.readInt(); + if (payloadSize >= maxPayloadSize) { + throw new IllegalStateException(format( + "Frame body is too large (%d), maximum configured size is %d. " + + "See ConnectionFactory#setMaxInboundMessageBodySize " + + "if you need to increase the limit.", + payloadSize, maxPayloadSize + )); + } byte[] payload = new byte[payloadSize]; is.readFully(payload); @@ -198,6 +201,14 @@ public void writeTo(DataOutputStream os) throws IOException { os.write(AMQP.FRAME_END); } + public int size() { + if(accumulator != null) { + return accumulator.size() + NON_BODY_SIZE; + } else { + return payload.length + NON_BODY_SIZE; + } + } + /** * Public API - retrieves the frame payload */ @@ -265,7 +276,7 @@ else if(value instanceof Integer) { else if(value instanceof BigDecimal) { acc += 5; } - else if(value instanceof Date || value instanceof Timestamp) { + else if(value instanceof Date) { acc += 8; } else if(value instanceof Map) { @@ -308,7 +319,7 @@ else if(value == null) { return acc; } - /** Computes the AMQP wire-protocol length of an encoded field-array of type List */ + /** Computes the AMQP 0-9-1 wire-protocol length of an encoded field-array of type List */ public static long arraySize(List values) throws UnsupportedEncodingException { @@ -341,4 +352,12 @@ private static int shortStrSize(String str) { return str.getBytes("utf-8").length + 1; } + + public int getType() { + return type; + } + + public int getChannel() { + return channel; + } } diff --git a/src/com/rabbitmq/client/impl/FrameHandler.java b/src/main/java/com/rabbitmq/client/impl/FrameHandler.java similarity index 69% rename from src/com/rabbitmq/client/impl/FrameHandler.java rename to src/main/java/com/rabbitmq/client/impl/FrameHandler.java index 47602f638e..c445bd34ff 100644 --- a/src/com/rabbitmq/client/impl/FrameHandler.java +++ b/src/main/java/com/rabbitmq/client/impl/FrameHandler.java @@ -1,30 +1,27 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; import java.io.IOException; -import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; /** * Interface to a frame handler. - *

- * Concurrency
+ *

Concurrency

* Implementations must be thread-safe, and not allow frames to be interleaved, either while reading or writing. */ @@ -51,6 +48,8 @@ public interface FrameHandler extends NetworkConnection { */ void sendHeader() throws IOException; + void initialize(AMQConnection connection); + /** * Read a {@link Frame} from the underlying data connection. * @return an incoming Frame, or null if there is none diff --git a/src/main/java/com/rabbitmq/client/impl/FrameHandlerFactory.java b/src/main/java/com/rabbitmq/client/impl/FrameHandlerFactory.java new file mode 100644 index 0000000000..6e7f01a2fc --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/FrameHandlerFactory.java @@ -0,0 +1,14 @@ +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.Address; + +import java.io.IOException; + +/** + * + */ +public interface FrameHandlerFactory { + + FrameHandler create(Address addr, String connectionName) throws IOException; + +} diff --git a/src/com/rabbitmq/client/impl/HeartbeatSender.java b/src/main/java/com/rabbitmq/client/impl/HeartbeatSender.java similarity index 75% rename from src/com/rabbitmq/client/impl/HeartbeatSender.java rename to src/main/java/com/rabbitmq/client/impl/HeartbeatSender.java index bbd0c7c196..e0d3737383 100644 --- a/src/com/rabbitmq/client/impl/HeartbeatSender.java +++ b/src/main/java/com/rabbitmq/client/impl/HeartbeatSender.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -43,6 +42,7 @@ final class HeartbeatSender { private final ThreadFactory threadFactory; private ScheduledExecutorService executor; + private final boolean privateExecutor; private ScheduledFuture future; @@ -50,8 +50,10 @@ final class HeartbeatSender { private volatile long lastActivityTime; - HeartbeatSender(FrameHandler frameHandler, ThreadFactory threadFactory) { + HeartbeatSender(FrameHandler frameHandler, ScheduledExecutorService heartbeatExecutor, ThreadFactory threadFactory) { this.frameHandler = frameHandler; + this.privateExecutor = (heartbeatExecutor == null); + this.executor = heartbeatExecutor; this.threadFactory = threadFactory; } @@ -106,14 +108,14 @@ public void shutdown() { this.future = null; } - if (this.executor != null) { + if (this.privateExecutor) { // to be safe, we shouldn't call shutdown holding the // monitor. executorToShutdown = this.executor; - - this.shutdown = true; - this.executor = null; } + + this.executor = null; + this.shutdown = true; } if(executorToShutdown != null) { executorToShutdown.shutdown(); @@ -128,6 +130,7 @@ private HeartbeatRunnable(long heartbeatNanos) { this.heartbeatNanos = heartbeatNanos; } + @Override public void run() { try { long now = System.nanoTime(); diff --git a/src/main/java/com/rabbitmq/client/impl/LogTrafficListener.java b/src/main/java/com/rabbitmq/client/impl/LogTrafficListener.java new file mode 100644 index 0000000000..ec76d76d19 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/LogTrafficListener.java @@ -0,0 +1,41 @@ +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.Command; +import com.rabbitmq.client.TrafficListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link TrafficListener} that logs {@link Command} at TRACE level. + *

+ * This implementation checks whether the TRACE log level + * is enabled before logging anything. This {@link TrafficListener} + * should only be activated for debugging purposes, not in a production + * environment. + * + * @see TrafficListener + * @see com.rabbitmq.client.ConnectionFactory#setTrafficListener(TrafficListener) + * @since 5.5.0 + */ +public class LogTrafficListener implements TrafficListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(LogTrafficListener.class); + + @Override + public void write(Command outboundCommand) { + if (shouldLog(outboundCommand)) { + LOGGER.trace("Outbound command: {}", outboundCommand); + } + } + + @Override + public void read(Command inboundCommand) { + if (shouldLog(inboundCommand)) { + LOGGER.trace("Inbound command: {}", inboundCommand); + } + } + + protected boolean shouldLog(Command command) { + return LOGGER.isTraceEnabled(); + } +} diff --git a/src/com/rabbitmq/client/impl/LongStringHelper.java b/src/main/java/com/rabbitmq/client/impl/LongStringHelper.java similarity index 62% rename from src/com/rabbitmq/client/impl/LongStringHelper.java rename to src/main/java/com/rabbitmq/client/impl/LongStringHelper.java index 8d18b70ecd..067e9ee2c1 100644 --- a/src/com/rabbitmq/client/impl/LongStringHelper.java +++ b/src/main/java/com/rabbitmq/client/impl/LongStringHelper.java @@ -1,118 +1,113 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.impl; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.Arrays; - -import com.rabbitmq.client.LongString; - -/** - * Utility for working with {@link LongString}s. - */ -public class LongStringHelper -{ - /** - * Private API - Implementation of {@link LongString}. When - * interpreting bytes as a string, uses UTF-8 encoding. - */ - private static class ByteArrayLongString - implements LongString - { - private final byte [] bytes; - - public ByteArrayLongString(byte[] bytes) - { - this.bytes = bytes; - } - - @Override public boolean equals(Object o) - { - if(o instanceof LongString) { - LongString other = (LongString)o; - return Arrays.equals(this.bytes, other.getBytes()); - } - - return false; - } - - @Override public int hashCode() - { - return Arrays.hashCode(this.bytes); - } - - /** {@inheritDoc} */ - public byte[] getBytes() - { - return bytes; - } - - /** {@inheritDoc} */ - public DataInputStream getStream() - throws IOException - { - return new DataInputStream(new ByteArrayInputStream(bytes)); - } - - /** {@inheritDoc} */ - public long length() - { - return bytes.length; - } - - @Override public String toString() - { - try { - return new String(bytes, "utf-8"); - } - catch (UnsupportedEncodingException e) { - throw new Error("utf-8 encoding support required"); - } - } - } - - /** - * Converts a String to a LongString using UTF-8 encoding. - * @param string the string to wrap - * @return a LongString wrapping it - */ - public static LongString asLongString(String string) - { - if (string==null) return null; - try { - return new ByteArrayLongString(string.getBytes("utf-8")); - } - catch (UnsupportedEncodingException e) { - throw new Error("utf-8 encoding support required"); - } - } - - /** - * Converts a binary block to a LongString. - * @param bytes the data to wrap - * @return a LongString wrapping it - */ - public static LongString asLongString(byte [] bytes) - { - if (bytes==null) return null; - return new ByteArrayLongString(bytes); - } -} +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.impl; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.Arrays; + +import com.rabbitmq.client.LongString; + +/** + * Utility for working with {@link LongString}s. + */ +public class LongStringHelper +{ + /** + * Private API - Implementation of {@link LongString}. When + * interpreting bytes as a string, uses UTF-8 encoding. + */ + private static class ByteArrayLongString + implements LongString + { + private final byte [] bytes; + + public ByteArrayLongString(byte[] bytes) + { + this.bytes = bytes; + } + + @Override public boolean equals(Object o) + { + if(o instanceof LongString) { + LongString other = (LongString)o; + return Arrays.equals(this.bytes, other.getBytes()); + } + + return false; + } + + @Override public int hashCode() + { + return Arrays.hashCode(this.bytes); + } + + /** {@inheritDoc} */ + @Override + public byte[] getBytes() + { + return bytes; + } + + /** {@inheritDoc} */ + @Override + public DataInputStream getStream() + throws IOException + { + return new DataInputStream(new ByteArrayInputStream(bytes)); + } + + /** {@inheritDoc} */ + @Override + public long length() + { + return bytes.length; + } + + @Override + public String toString() + { + return new String(bytes, Charset.forName("utf-8")); + } + } + + /** + * Converts a String to a LongString using UTF-8 encoding. + * @param string the string to wrap + * @return a LongString wrapping it + */ + public static LongString asLongString(String string) + { + if (string == null) + return null; + return new ByteArrayLongString(string.getBytes(Charset.forName("utf-8"))); + } + + /** + * Converts a binary block to a LongString. + * @param bytes the data to wrap + * @return a LongString wrapping it + */ + public static LongString asLongString(byte [] bytes) + { + if (bytes==null) return null; + return new ByteArrayLongString(bytes); + } +} diff --git a/src/com/rabbitmq/client/impl/Method.java b/src/main/java/com/rabbitmq/client/impl/Method.java similarity index 74% rename from src/com/rabbitmq/client/impl/Method.java rename to src/main/java/com/rabbitmq/client/impl/Method.java index 0a7ce83808..52a42e0e34 100644 --- a/src/com/rabbitmq/client/impl/Method.java +++ b/src/main/java/com/rabbitmq/client/impl/Method.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -29,12 +28,15 @@ */ public abstract class Method implements com.rabbitmq.client.Method { /** {@inheritDoc} */ + @Override public abstract int protocolClassId(); /* properly an unsigned short */ /** {@inheritDoc} */ + @Override public abstract int protocolMethodId(); /* properly an unsigned short */ /** {@inheritDoc} */ + @Override public abstract String protocolMethodName(); /** diff --git a/src/com/rabbitmq/client/impl/MethodArgumentReader.java b/src/main/java/com/rabbitmq/client/impl/MethodArgumentReader.java similarity index 79% rename from src/com/rabbitmq/client/impl/MethodArgumentReader.java rename to src/main/java/com/rabbitmq/client/impl/MethodArgumentReader.java index c7c1973212..0139f765a4 100644 --- a/src/com/rabbitmq/client/impl/MethodArgumentReader.java +++ b/src/main/java/com/rabbitmq/client/impl/MethodArgumentReader.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; diff --git a/src/com/rabbitmq/client/impl/MethodArgumentWriter.java b/src/main/java/com/rabbitmq/client/impl/MethodArgumentWriter.java similarity index 81% rename from src/com/rabbitmq/client/impl/MethodArgumentWriter.java rename to src/main/java/com/rabbitmq/client/impl/MethodArgumentWriter.java index 2143ea9b16..2c90236ea1 100644 --- a/src/com/rabbitmq/client/impl/MethodArgumentWriter.java +++ b/src/main/java/com/rabbitmq/client/impl/MethodArgumentWriter.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -39,7 +38,7 @@ public class MethodArgumentWriter private int bitMask; /** - * Constructs a MethodArgumentWriter targetting the given DataOutputStream. + * Constructs a MethodArgumentWriter targeting the given DataOutputStream. */ public MethodArgumentWriter(ValueWriter out) { @@ -58,7 +57,7 @@ private void resetBitAccumulator() { * Private API - called when we may be transitioning from encoding * a group of bits to encoding a non-bit value. */ - private final void bitflush() + private void bitflush() throws IOException { if (needBitFlush) { diff --git a/src/main/java/com/rabbitmq/client/impl/MicrometerMetricsCollector.java b/src/main/java/com/rabbitmq/client/impl/MicrometerMetricsCollector.java new file mode 100644 index 0000000000..85fbd9bf88 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/MicrometerMetricsCollector.java @@ -0,0 +1,283 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.MetricsCollector; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; + +import static com.rabbitmq.client.impl.MicrometerMetricsCollector.Metrics.*; + +/** + * Micrometer implementation of {@link MetricsCollector}. + * Note transactions are not supported (see {@link MetricsCollector}. + * Micrometer provides out-of-the-box support for report backends like JMX, + * Graphite, Ganglia, Atlas, Datadog, etc. See Micrometer documentation for + * more details. + * + * Note Micrometer requires Java 8 or more, so does this {@link MetricsCollector}. + * + * @see MetricsCollector + * @since 4.3.0 + */ +public class MicrometerMetricsCollector extends AbstractMetricsCollector { + + private final AtomicLong connections; + + private final AtomicLong channels; + + private final Counter publishedMessages; + + private final Counter failedToPublishMessages; + + private final Counter ackedPublishedMessages; + + private final Counter nackedPublishedMessages; + + private final Counter unroutedPublishedMessages; + + private final Counter consumedMessages; + + private final Counter acknowledgedMessages; + + private final Counter rejectedMessages; + + private final Counter requeuedMessages; + + public MicrometerMetricsCollector(MeterRegistry registry) { + this(registry, "rabbitmq"); + } + + public MicrometerMetricsCollector(final MeterRegistry registry, final String prefix) { + this(registry, prefix, Collections.emptyList()); + } + + public MicrometerMetricsCollector(final MeterRegistry registry, final String prefix, final String ... tags) { + this(registry, prefix, Tags.of(tags)); + } + + public MicrometerMetricsCollector(final MeterRegistry registry, final String prefix, final Iterable tags) { + this(metric -> metric.create(registry, prefix, tags)); + } + + public MicrometerMetricsCollector(Function metricsCreator) { + this.connections = (AtomicLong) metricsCreator.apply(CONNECTIONS); + this.channels = (AtomicLong) metricsCreator.apply(CHANNELS); + this.publishedMessages = (Counter) metricsCreator.apply(PUBLISHED_MESSAGES); + this.consumedMessages = (Counter) metricsCreator.apply(CONSUMED_MESSAGES); + this.acknowledgedMessages = (Counter) metricsCreator.apply(ACKNOWLEDGED_MESSAGES); + this.rejectedMessages = (Counter) metricsCreator.apply(REJECTED_MESSAGES); + this.failedToPublishMessages = (Counter) metricsCreator.apply(FAILED_TO_PUBLISH_MESSAGES); + this.ackedPublishedMessages = (Counter) metricsCreator.apply(ACKED_PUBLISHED_MESSAGES); + this.nackedPublishedMessages = (Counter) metricsCreator.apply(NACKED_PUBLISHED_MESSAGES); + this.unroutedPublishedMessages = (Counter) metricsCreator.apply(UNROUTED_PUBLISHED_MESSAGES); + this.requeuedMessages = (Counter) metricsCreator.apply(REQUEUED_MESSAGES); + } + + @Override + protected void incrementConnectionCount(Connection connection) { + connections.incrementAndGet(); + } + + @Override + protected void decrementConnectionCount(Connection connection) { + connections.decrementAndGet(); + } + + @Override + protected void incrementChannelCount(Channel channel) { + channels.incrementAndGet(); + } + + @Override + protected void decrementChannelCount(Channel channel) { + channels.decrementAndGet(); + } + + @Override + protected void markPublishedMessage() { + publishedMessages.increment(); + } + + @Override + protected void markMessagePublishFailed() { + failedToPublishMessages.increment(); + } + + @Override + protected void markConsumedMessage() { + consumedMessages.increment(); + } + + @Override + protected void markAcknowledgedMessage() { + acknowledgedMessages.increment(); + } + + @Override + @SuppressWarnings("deprecation") + protected void markRejectedMessage() { + + } + + @Override + protected void markRejectedMessage(boolean requeue) { + if (requeue) { + requeuedMessages.increment(); + } + rejectedMessages.increment(); + } + + @Override + protected void markMessagePublishAcknowledged() { + ackedPublishedMessages.increment(); + } + + @Override + protected void markMessagePublishNotAcknowledged() { + nackedPublishedMessages.increment(); + } + + @Override + protected void markPublishedMessageUnrouted() { + unroutedPublishedMessages.increment(); + } + + public AtomicLong getConnections() { + return connections; + } + + public AtomicLong getChannels() { + return channels; + } + + public Counter getPublishedMessages() { + return publishedMessages; + } + + public Counter getFailedToPublishMessages() { + return failedToPublishMessages; + } + + public Counter getAckedPublishedMessages() { + return ackedPublishedMessages; + } + + public Counter getNackedPublishedMessages() { + return nackedPublishedMessages; + } + + public Counter getUnroutedPublishedMessages() { + return unroutedPublishedMessages; + } + + public Counter getConsumedMessages() { + return consumedMessages; + } + + public Counter getAcknowledgedMessages() { + return acknowledgedMessages; + } + + public Counter getRejectedMessages() { + return rejectedMessages; + } + + public Counter getRequeuedMessages() { + return requeuedMessages; + } + + public enum Metrics { + CONNECTIONS { + @Override + Object create(MeterRegistry registry, String prefix, Iterable tags) { + return registry.gauge(prefix + ".connections", tags, new AtomicLong(0)); + } + }, + CHANNELS { + @Override + Object create(MeterRegistry registry, String prefix, Iterable tags) { + return registry.gauge(prefix + ".channels", tags, new AtomicLong(0)); + } + }, + PUBLISHED_MESSAGES { + @Override + Object create(MeterRegistry registry, String prefix, Iterable tags) { + return registry.counter(prefix + ".published", tags); + } + }, + CONSUMED_MESSAGES { + @Override + Object create(MeterRegistry registry, String prefix, Iterable tags) { + return registry.counter(prefix + ".consumed", tags); + } + }, + ACKNOWLEDGED_MESSAGES { + @Override + Object create(MeterRegistry registry, String prefix, Iterable tags) { + return registry.counter(prefix + ".acknowledged", tags); + } + }, + REJECTED_MESSAGES { + @Override + Object create(MeterRegistry registry, String prefix, Iterable tags) { + return registry.counter(prefix + ".rejected", tags); + } + }, + REQUEUED_MESSAGES { + @Override + Object create(MeterRegistry registry, String prefix, Iterable tags) { + return registry.counter(prefix + ".requeued", tags); + } + }, + FAILED_TO_PUBLISH_MESSAGES { + @Override + Object create(MeterRegistry registry, String prefix, Iterable tags) { + return registry.counter(prefix + ".failed_to_publish", tags); + } + }, + ACKED_PUBLISHED_MESSAGES { + @Override + Object create(MeterRegistry registry, String prefix, Iterable tags) { + return registry.counter(prefix + ".acknowledged_published", tags); + } + }, + NACKED_PUBLISHED_MESSAGES { + @Override + Object create(MeterRegistry registry, String prefix, Iterable tags) { + return registry.counter(prefix + ".not_acknowledged_published", tags); + } + }, + UNROUTED_PUBLISHED_MESSAGES { + @Override + Object create(MeterRegistry registry, String prefix, Iterable tags) { + return registry.counter(prefix + ".unrouted_published", tags); + } + }; + + abstract Object create(MeterRegistry registry, String prefix, Iterable tags); + + } + +} \ No newline at end of file diff --git a/src/main/java/com/rabbitmq/client/impl/NetworkConnection.java b/src/main/java/com/rabbitmq/client/impl/NetworkConnection.java new file mode 100644 index 0000000000..11ed9a5314 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/NetworkConnection.java @@ -0,0 +1,39 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import java.net.InetAddress; + +public interface NetworkConnection { + + /** + * Retrieve the local host. + * @return the client socket address. + */ + InetAddress getLocalAddress(); + + /** + * Retrieve the local port number. + * @return the client socket port number + */ + int getLocalPort(); + + /** Retrieve address of peer. */ + InetAddress getAddress(); + + /** Retrieve port number of peer. */ + int getPort(); +} diff --git a/src/main/java/com/rabbitmq/client/impl/OAuth2ClientCredentialsGrantCredentialsProvider.java b/src/main/java/com/rabbitmq/client/impl/OAuth2ClientCredentialsGrantCredentialsProvider.java new file mode 100644 index 0000000000..43c3b392c3 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/OAuth2ClientCredentialsGrantCredentialsProvider.java @@ -0,0 +1,609 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rabbitmq.client.TrustEverythingTrustManager; + +import java.net.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import javax.net.ssl.*; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.function.Consumer; + +import static com.rabbitmq.client.ConnectionFactory.computeDefaultTlsProtocol; + +/** + * A {@link CredentialsProvider} that performs an + * OAuth 2 Client Credentials flow + * to retrieve a token. + *

+ * The provider has different parameters to set, e.g. the token endpoint URI of the OAuth server to + * request, the client ID, the client secret, the grant type, etc. The {@link OAuth2ClientCredentialsGrantCredentialsProviderBuilder} + * class is the preferred way to create an instance of the provider. + *

+ * The implementation uses the JDK {@link HttpURLConnection} API to request the OAuth server. This can + * be easily changed by overriding the {@link #retrieveToken()} method. + *

+ * This class expects a JSON document as a response and needs Jackson + * to deserialize the response into a {@link Token}. This can be changed by overriding the {@link #parseToken(String)} + * method. + *

+ * TLS is supported by providing a HTTPS URI and setting a {@link SSLContext}. See + * {@link OAuth2ClientCredentialsGrantCredentialsProviderBuilder#tls()} for more information. + * Applications in production should always use HTTPS to retrieve tokens. + *

+ * If more customization is needed, a {@link #connectionConfigurator} callback can be provided to configure + * the connection. + * + * @see RefreshProtectedCredentialsProvider + * @see CredentialsRefreshService + * @see OAuth2ClientCredentialsGrantCredentialsProviderBuilder + * @see OAuth2ClientCredentialsGrantCredentialsProviderBuilder#tls() + */ +public class OAuth2ClientCredentialsGrantCredentialsProvider extends RefreshProtectedCredentialsProvider { + + private static final String UTF_8_CHARSET = "UTF-8"; + private final String tokenEndpointUri; + private final String clientId; + private final String clientSecret; + private final String grantType; + + private final Map parameters; + + private final AtomicReference> tokenExtractor = new AtomicReference<>(); + + private final String id; + + private final HostnameVerifier hostnameVerifier; + private final SSLSocketFactory sslSocketFactory; + + private final Consumer connectionConfigurator; + + /** + * Use {@link OAuth2ClientCredentialsGrantCredentialsProviderBuilder} to create an instance. + * + * @param tokenEndpointUri + * @param clientId + * @param clientSecret + * @param grantType + */ + public OAuth2ClientCredentialsGrantCredentialsProvider(String tokenEndpointUri, String clientId, String clientSecret, String grantType) { + this(tokenEndpointUri, clientId, clientSecret, grantType, new HashMap<>()); + } + + /** + * Use {@link OAuth2ClientCredentialsGrantCredentialsProviderBuilder} to create an instance. + * + * @param tokenEndpointUri + * @param clientId + * @param clientSecret + * @param grantType + * @param parameters + */ + public OAuth2ClientCredentialsGrantCredentialsProvider(String tokenEndpointUri, String clientId, String clientSecret, String grantType, Map parameters) { + this(tokenEndpointUri, clientId, clientSecret, grantType, parameters, null, null, null); + } + + /** + * Use {@link OAuth2ClientCredentialsGrantCredentialsProviderBuilder} to create an instance. + * + * @param tokenEndpointUri + * @param clientId + * @param clientSecret + * @param grantType + * @param parameters + * @param connectionConfigurator + */ + public OAuth2ClientCredentialsGrantCredentialsProvider(String tokenEndpointUri, String clientId, String clientSecret, String grantType, Map parameters, + Consumer connectionConfigurator) { + this(tokenEndpointUri, clientId, clientSecret, grantType, parameters, null, null, connectionConfigurator); + } + + /** + * Use {@link OAuth2ClientCredentialsGrantCredentialsProviderBuilder} to create an instance. + * + * @param tokenEndpointUri + * @param clientId + * @param clientSecret + * @param grantType + * @param hostnameVerifier + * @param sslSocketFactory + */ + public OAuth2ClientCredentialsGrantCredentialsProvider(String tokenEndpointUri, String clientId, String clientSecret, String grantType, + HostnameVerifier hostnameVerifier, SSLSocketFactory sslSocketFactory) { + this(tokenEndpointUri, clientId, clientSecret, grantType, new HashMap<>(), hostnameVerifier, sslSocketFactory, null); + } + + /** + * Use {@link OAuth2ClientCredentialsGrantCredentialsProviderBuilder} to create an instance. + * + * @param tokenEndpointUri + * @param clientId + * @param clientSecret + * @param grantType + * @param parameters + * @param hostnameVerifier + * @param sslSocketFactory + */ + public OAuth2ClientCredentialsGrantCredentialsProvider(String tokenEndpointUri, String clientId, String clientSecret, String grantType, Map parameters, + HostnameVerifier hostnameVerifier, SSLSocketFactory sslSocketFactory) { + this(tokenEndpointUri, clientId, clientSecret, grantType, parameters, hostnameVerifier, sslSocketFactory, null); + } + + /** + * Use {@link OAuth2ClientCredentialsGrantCredentialsProviderBuilder} to create an instance. + * + * @param tokenEndpointUri + * @param clientId + * @param clientSecret + * @param grantType + * @param parameters + * @param hostnameVerifier + * @param sslSocketFactory + * @param connectionConfigurator + */ + public OAuth2ClientCredentialsGrantCredentialsProvider(String tokenEndpointUri, String clientId, String clientSecret, String grantType, Map parameters, + HostnameVerifier hostnameVerifier, SSLSocketFactory sslSocketFactory, + Consumer connectionConfigurator) { + this.tokenEndpointUri = tokenEndpointUri; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.grantType = grantType; + this.parameters = Collections.unmodifiableMap(new HashMap<>(parameters)); + this.hostnameVerifier = hostnameVerifier; + this.sslSocketFactory = sslSocketFactory; + this.connectionConfigurator = connectionConfigurator == null ? c -> { + } : connectionConfigurator; + this.id = UUID.randomUUID().toString(); + } + + private static StringBuilder encode(StringBuilder builder, String name, String value) throws UnsupportedEncodingException { + if (value != null) { + if (builder.length() > 0) { + builder.append("&"); + } + builder.append(encode(name, UTF_8_CHARSET)) + .append("=") + .append(encode(value, UTF_8_CHARSET)); + } + return builder; + } + + private static String encode(String value, String charset) throws UnsupportedEncodingException { + return URLEncoder.encode(value, charset); + } + + private static String basicAuthentication(String username, String password) { + String credentials = username + ":" + password; + byte[] credentialsAsBytes = credentials.getBytes(StandardCharsets.ISO_8859_1); + byte[] encodedBytes = Base64.getEncoder().encode(credentialsAsBytes); + String encodedCredentials = new String(encodedBytes, StandardCharsets.ISO_8859_1); + return "Basic " + encodedCredentials; + } + + @Override + public String getUsername() { + return ""; + } + + @Override + protected String usernameFromToken(Token token) { + return ""; + } + + protected Token parseToken(String response) { + return this.tokenExtractor.updateAndGet(current -> + current == null ? new JacksonTokenLookup() : current).apply(response); + } + + @Override + protected Token retrieveToken() { + try { + StringBuilder urlParameters = new StringBuilder(); + encode(urlParameters, "grant_type", grantType); + for (Map.Entry parameter : parameters.entrySet()) { + encode(urlParameters, parameter.getKey(), parameter.getValue()); + } + byte[] postData = urlParameters.toString().getBytes(StandardCharsets.UTF_8); + int postDataLength = postData.length; + URL url = new URI(tokenEndpointUri).toURL(); + + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + conn.setDoOutput(true); + conn.setInstanceFollowRedirects(false); + conn.setRequestMethod("POST"); + conn.setRequestProperty("authorization", basicAuthentication(clientId, clientSecret)); + conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); + conn.setRequestProperty("charset", UTF_8_CHARSET); + conn.setRequestProperty("accept", "application/json"); + conn.setRequestProperty("content-length", Integer.toString(postDataLength)); + conn.setUseCaches(false); + conn.setConnectTimeout(60_000); + conn.setReadTimeout(60_000); + + configureConnection(conn); + + try (DataOutputStream wr = new DataOutputStream(conn.getOutputStream())) { + wr.write(postData); + } + + checkResponseCode(conn.getResponseCode()); + checkContentType(conn.getHeaderField("content-type")); + + return parseToken(extractResponseBody(conn.getInputStream())); + } catch (IOException | URISyntaxException e) { + throw new OAuthTokenManagementException("Error while retrieving OAuth 2 token", e); + } + } + + protected void checkContentType(String headerField) throws OAuthTokenManagementException { + if (headerField == null || !headerField.toLowerCase().contains("json")) { + throw new OAuthTokenManagementException( + "HTTP request for token retrieval is not JSON: " + headerField + ); + } + } + + protected void checkResponseCode(int responseCode) throws OAuthTokenManagementException { + if (responseCode != 200) { + throw new OAuthTokenManagementException( + "HTTP request for token retrieval did not " + + "return 200 response code: " + responseCode + ); + } + } + + protected String extractResponseBody(InputStream inputStream) throws IOException { + StringBuffer content = new StringBuffer(); + try (BufferedReader in = new BufferedReader(new InputStreamReader(inputStream))) { + String inputLine; + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + } + return content.toString(); + } + + @Override + protected String passwordFromToken(Token token) { + return token.getAccess(); + } + + @Override + protected Duration timeBeforeExpiration(Token token) { + return token.getTimeBeforeExpiration(); + } + + protected void configureConnection(HttpURLConnection connection) { + this.connectionConfigurator.accept(connection); + this.configureConnectionForHttps(connection); + } + + protected void configureConnectionForHttps(HttpURLConnection connection) { + if (connection instanceof HttpsURLConnection) { + HttpsURLConnection securedConnection = (HttpsURLConnection) connection; + if (this.hostnameVerifier != null) { + securedConnection.setHostnameVerifier(this.hostnameVerifier); + } + if (this.sslSocketFactory != null) { + securedConnection.setSSLSocketFactory(this.sslSocketFactory); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + OAuth2ClientCredentialsGrantCredentialsProvider that = (OAuth2ClientCredentialsGrantCredentialsProvider) o; + + return id.equals(that.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + public static class Token { + + private final String access; + + private final int expiresIn; + + private final Instant receivedAt; + + public Token(String access, int expiresIn, Instant receivedAt) { + this.access = access; + this.expiresIn = expiresIn; + this.receivedAt = receivedAt; + } + + public String getAccess() { + return access; + } + + public int getExpiresIn() { + return expiresIn; + } + + public Instant getReceivedAt() { + return receivedAt; + } + + public Duration getTimeBeforeExpiration() { + Instant now = Instant.now(); + long age = receivedAt.until(now, ChronoUnit.SECONDS); + return Duration.ofSeconds(expiresIn - age); + } + } + + /** + * Helper to create {@link OAuth2ClientCredentialsGrantCredentialsProvider} instances. + */ + public static class OAuth2ClientCredentialsGrantCredentialsProviderBuilder { + + private final Map parameters = new HashMap<>(); + private String tokenEndpointUri; + private String clientId; + private String clientSecret; + private String grantType = "client_credentials"; + + private Consumer connectionConfigurator; + + private TlsConfiguration tlsConfiguration = new TlsConfiguration(this); + + /** + * Set the URI to request to get the token. + * + * @param tokenEndpointUri + * @return this builder instance + */ + public OAuth2ClientCredentialsGrantCredentialsProviderBuilder tokenEndpointUri(String tokenEndpointUri) { + this.tokenEndpointUri = tokenEndpointUri; + return this; + } + + /** + * Set the OAuth 2 client ID + *

+ * The client ID usually identifies the application that requests a token. + * + * @param clientId + * @return this builder instance + */ + public OAuth2ClientCredentialsGrantCredentialsProviderBuilder clientId(String clientId) { + this.clientId = clientId; + return this; + } + + /** + * Set the secret (password) to use to get a token. + * + * @param clientSecret + * @return this builder instance + */ + public OAuth2ClientCredentialsGrantCredentialsProviderBuilder clientSecret(String clientSecret) { + this.clientSecret = clientSecret; + return this; + } + + /** + * Set the grant type to use when requesting the token. + *

+ * The default is client_credentials, but some OAuth 2 servers can use + * non-standard grant types to request tokens with extra-information. + * + * @param grantType + * @return this builder instance + */ + public OAuth2ClientCredentialsGrantCredentialsProviderBuilder grantType(String grantType) { + this.grantType = grantType; + return this; + } + + /** + * Extra parameters to pass in the request. + *

+ * These parameters can be used by the OAuth 2 server to narrow down the identify of the user. + * + * @param name + * @param value + * @return this builder instance + */ + public OAuth2ClientCredentialsGrantCredentialsProviderBuilder parameter(String name, String value) { + this.parameters.put(name, value); + return this; + } + + /** + * A hook to configure the {@link HttpURLConnection} before the request is sent. + *

+ * Can be used to configuration settings like timeouts. + * + * @param connectionConfigurator + * @return this builder instance + */ + public OAuth2ClientCredentialsGrantCredentialsProviderBuilder connectionConfigurator(Consumer connectionConfigurator) { + this.connectionConfigurator = connectionConfigurator; + return this; + } + + /** + * Get access to the TLS configuration to get the token on HTTPS. + *

+ * It is recommended that applications in production use HTTPS and configure it properly + * to perform token retrieval. Not doing so could result in sensitive data + * transiting in clear on the network. + *

+ * You can "exit" the TLS configuration and come back to the builder by + * calling {@link TlsConfiguration#builder()}. + * + * @return the TLS configuration for this builder. + * @see TlsConfiguration + * @see TlsConfiguration#builder() + */ + public TlsConfiguration tls() { + return this.tlsConfiguration; + } + + /** + * Create the {@link OAuth2ClientCredentialsGrantCredentialsProvider} instance. + * + * @return + */ + public OAuth2ClientCredentialsGrantCredentialsProvider build() { + return new OAuth2ClientCredentialsGrantCredentialsProvider( + tokenEndpointUri, clientId, clientSecret, grantType, parameters, + tlsConfiguration.hostnameVerifier, tlsConfiguration.sslSocketFactory(), + connectionConfigurator + ); + } + + } + + /** + * TLS configuration for a {@link OAuth2ClientCredentialsGrantCredentialsProvider}. + *

+ * Use it from {@link OAuth2ClientCredentialsGrantCredentialsProviderBuilder#tls()}. + */ + public static class TlsConfiguration { + + private final OAuth2ClientCredentialsGrantCredentialsProviderBuilder builder; + + private HostnameVerifier hostnameVerifier; + + private SSLSocketFactory sslSocketFactory; + + private SSLContext sslContext; + + public TlsConfiguration(OAuth2ClientCredentialsGrantCredentialsProviderBuilder builder) { + this.builder = builder; + } + + /** + * Set the hostname verifier. + *

+ * {@link HttpsURLConnection} sets a default hostname verifier, so + * setting a custom one is only needed for specific cases. + * + * @param hostnameVerifier + * @return this TLS configuration instance + * @see HostnameVerifier + */ + public TlsConfiguration hostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + return this; + } + + /** + * Set the {@link SSLSocketFactory} to use in the {@link HttpsURLConnection}. + *

+ * The {@link SSLSocketFactory} supersedes the {@link SSLContext} value if both are set up. + * + * @param sslSocketFactory + * @return this TLS configuration instance + */ + public TlsConfiguration sslSocketFactory(SSLSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + return this; + } + + /** + * Set the {@link SSLContext} to use to create the {@link SSLSocketFactory} for the {@link HttpsURLConnection}. + *

+ * This is the preferred way to configure TLS version to use, trusted servers, etc. + *

+ * Note the {@link SSLContext} is not used if the {@link SSLSocketFactory} is set. + * + * @param sslContext + * @return this TLS configuration instances + */ + public TlsConfiguration sslContext(SSLContext sslContext) { + this.sslContext = sslContext; + return this; + } + + /** + * Set up a non-secured environment, useful for development and testing. + *

+ * With this configuration, all servers are trusted. + * + * DO NOT USE this in production. + * + * @return a TLS configuration that trusts all servers + */ + public TlsConfiguration dev() { + try { + SSLContext sslContext = SSLContext.getInstance(computeDefaultTlsProtocol( + SSLContext.getDefault().getSupportedSSLParameters().getProtocols() + )); + sslContext.init(null, new TrustManager[]{new TrustEverythingTrustManager()}, null); + this.sslContext = sslContext; + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new OAuthTokenManagementException("Error while creating TLS context for development configuration", e); + } + return this; + } + + /** + * Go back to the builder to configure non-TLS settings. + * + * @return the wrapping builder + */ + public OAuth2ClientCredentialsGrantCredentialsProviderBuilder builder() { + return builder; + } + + private SSLSocketFactory sslSocketFactory() { + if (this.sslSocketFactory != null) { + return this.sslSocketFactory; + } else if (this.sslContext != null) { + return this.sslContext.getSocketFactory(); + } + return null; + } + + } + + private static class JacksonTokenLookup implements Function { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public Token apply(String response) { + try { + Map map = objectMapper.readValue(response, Map.class); + int expiresIn = ((Number) map.get("expires_in")).intValue(); + Instant receivedAt = Instant.now(); + return new Token(map.get("access_token").toString(), expiresIn, receivedAt); + } catch (IOException e) { + throw new OAuthTokenManagementException("Error while parsing OAuth 2 token", e); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/rabbitmq/client/impl/OAuthTokenManagementException.java b/src/main/java/com/rabbitmq/client/impl/OAuthTokenManagementException.java new file mode 100644 index 0000000000..595b74dd19 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/OAuthTokenManagementException.java @@ -0,0 +1,27 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +public class OAuthTokenManagementException extends RuntimeException { + + public OAuthTokenManagementException(String message, Throwable cause) { + super(message, cause); + } + + public OAuthTokenManagementException(String message) { + super(message); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/OpenTelemetryMetricsCollector.java b/src/main/java/com/rabbitmq/client/impl/OpenTelemetryMetricsCollector.java new file mode 100644 index 0000000000..6933927116 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/OpenTelemetryMetricsCollector.java @@ -0,0 +1,211 @@ +// Copyright (c) 2023-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.MetricsCollector; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * OpenTelemetry implementation of {@link MetricsCollector}. + * + * @see MetricsCollector + * @since 5.16.0 + */ +public class OpenTelemetryMetricsCollector extends AbstractMetricsCollector { + + private final Attributes attributes; + + private final AtomicLong connections = new AtomicLong(0L); + private final AtomicLong channels = new AtomicLong(0L); + + private final LongCounter publishedMessagesCounter; + private final LongCounter consumedMessagesCounter; + private final LongCounter acknowledgedMessagesCounter; + private final LongCounter rejectedMessagesCounter; + private final LongCounter failedToPublishMessagesCounter; + private final LongCounter ackedPublishedMessagesCounter; + private final LongCounter nackedPublishedMessagesCounter; + private final LongCounter unroutedPublishedMessagesCounter; + private final LongCounter requeuedMessagesCounter; + + public OpenTelemetryMetricsCollector(OpenTelemetry openTelemetry) { + this(openTelemetry, "rabbitmq"); + } + + public OpenTelemetryMetricsCollector(final OpenTelemetry openTelemetry, final String prefix) { + this(openTelemetry, prefix, Attributes.empty()); + } + + public OpenTelemetryMetricsCollector(final OpenTelemetry openTelemetry, final String prefix, final Attributes attributes) { + // initialize meter + Meter meter = openTelemetry.getMeter("amqp-client"); + + // attributes + this.attributes = attributes; + + // connections + meter.gaugeBuilder(prefix + ".connections") + .setUnit("{connections}") + .setDescription("The number of connections to the RabbitMQ server") + .ofLongs() + .buildWithCallback(measurement -> measurement.record(connections.get(), attributes)); + + // channels + meter.gaugeBuilder(prefix + ".channels") + .setUnit("{channels}") + .setDescription("The number of channels to the RabbitMQ server") + .ofLongs() + .buildWithCallback(measurement -> measurement.record(channels.get(), attributes)); + + // publishedMessages + this.publishedMessagesCounter = meter.counterBuilder(prefix + ".published") + .setUnit("{messages}") + .setDescription("The number of messages published to the RabbitMQ server") + .build(); + + // consumedMessages + this.consumedMessagesCounter = meter.counterBuilder(prefix + ".consumed") + .setUnit("{messages}") + .setDescription("The number of messages consumed from the RabbitMQ server") + .build(); + + // acknowledgedMessages + this.acknowledgedMessagesCounter = meter.counterBuilder(prefix + ".acknowledged") + .setUnit("{messages}") + .setDescription("The number of messages acknowledged from the RabbitMQ server") + .build(); + + // rejectedMessages + this.rejectedMessagesCounter = meter.counterBuilder(prefix + ".rejected") + .setUnit("{messages}") + .setDescription("The number of messages rejected from the RabbitMQ server") + .build(); + + // requeuedPublishedMessages + this.requeuedMessagesCounter = meter.counterBuilder(prefix + ".requeued") + .setUnit("{messages}") + .setDescription("The number of re-queued messages to the RabbitMQ server") + .build(); + + // failedToPublishMessages + this.failedToPublishMessagesCounter = meter.counterBuilder(prefix + ".failed_to_publish") + .setUnit("{messages}") + .setDescription("The number of messages failed to publish to the RabbitMQ server") + .build(); + + // ackedPublishedMessages + this.ackedPublishedMessagesCounter = meter.counterBuilder(prefix + ".acknowledged_published") + .setUnit("{messages}") + .setDescription("The number of published messages acknowledged by the RabbitMQ server") + .build(); + + // nackedPublishedMessages + this.nackedPublishedMessagesCounter = meter.counterBuilder(prefix + ".not_acknowledged_published") + .setUnit("{messages}") + .setDescription("The number of published messages not acknowledged by the RabbitMQ server") + .build(); + + // unroutedPublishedMessages + this.unroutedPublishedMessagesCounter = meter.counterBuilder(prefix + ".unrouted_published") + .setUnit("{messages}") + .setDescription("The number of un-routed published messages to the RabbitMQ server") + .build(); + } + + @Override + protected void incrementConnectionCount(Connection connection) { + connections.incrementAndGet(); + } + + @Override + protected void decrementConnectionCount(Connection connection) { + connections.decrementAndGet(); + } + + @Override + protected void incrementChannelCount(Channel channel) { + channels.incrementAndGet(); + } + + @Override + protected void decrementChannelCount(Channel channel) { + channels.decrementAndGet(); + } + + @Override + protected void markPublishedMessage() { + publishedMessagesCounter.add(1L, attributes); + } + + @Override + protected void markMessagePublishFailed() { + failedToPublishMessagesCounter.add(1L, attributes); + } + + @Override + protected void markConsumedMessage() { + consumedMessagesCounter.add(1L, attributes); + } + + @Override + protected void markAcknowledgedMessage() { + acknowledgedMessagesCounter.add(1L, attributes); + } + + @Override + @SuppressWarnings("deprecation") + protected void markRejectedMessage() { + + } + + @Override + protected void markRejectedMessage(boolean requeue) { + if (requeue) { + requeuedMessagesCounter.add(1L, attributes); + } + rejectedMessagesCounter.add(1L, attributes); + } + + @Override + protected void markMessagePublishAcknowledged() { + ackedPublishedMessagesCounter.add(1L, attributes); + } + + @Override + protected void markMessagePublishNotAcknowledged() { + nackedPublishedMessagesCounter.add(1L, attributes); + } + + @Override + protected void markPublishedMessageUnrouted() { + unroutedPublishedMessagesCounter.add(1L, attributes); + } + + public AtomicLong getConnections() { + return connections; + } + + public AtomicLong getChannels() { + return channels; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/PlainMechanism.java b/src/main/java/com/rabbitmq/client/impl/PlainMechanism.java new file mode 100644 index 0000000000..485d382933 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/PlainMechanism.java @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.LongString; +import com.rabbitmq.client.SaslMechanism; + +/** + * The PLAIN auth mechanism + */ +public class PlainMechanism implements SaslMechanism { + @Override + public String getName() { + return "PLAIN"; + } + + @Override + public LongString handleChallenge(LongString challenge, + String username, + String password) { + return LongStringHelper.asLongString("\0" + username + + "\0" + password); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/RefreshProtectedCredentialsProvider.java b/src/main/java/com/rabbitmq/client/impl/RefreshProtectedCredentialsProvider.java new file mode 100644 index 0000000000..80a6112793 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/RefreshProtectedCredentialsProvider.java @@ -0,0 +1,113 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * An abstract {@link CredentialsProvider} that does not let token refresh happen concurrently. + *

+ * A token is usually long-lived (several minutes or more), can be re-used inside the same application, + * and refreshing it is a costly operation. This base class lets a first call to {@link #refresh()} + * pass and block concurrent calls until the first call is over. Concurrent calls are then unblocked and + * can benefit from the refresh. This avoids unnecessary refresh operations to happen if a token + * is already being renewed. + *

+ * Subclasses need to provide the actual token retrieval (whether is a first retrieval or a renewal is + * a implementation detail) and how to extract information (username, password, time before expiration) + * from the retrieved token. + * + * @param the type of token (usually specified by the subclass) + */ +public abstract class RefreshProtectedCredentialsProvider implements CredentialsProvider { + + private static final Logger LOGGER = LoggerFactory.getLogger(RefreshProtectedCredentialsProvider.class); + + private final AtomicReference token = new AtomicReference<>(); + + private final Lock refreshLock = new ReentrantLock(); + private final AtomicReference latch = new AtomicReference<>(); + private final AtomicBoolean refreshInProcess = new AtomicBoolean(false); + + @Override + public String getUsername() { + if (token.get() == null) { + refresh(); + } + return usernameFromToken(token.get()); + } + + @Override + public String getPassword() { + if (token.get() == null) { + refresh(); + } + return passwordFromToken(token.get()); + } + + @Override + public Duration getTimeBeforeExpiration() { + if (token.get() == null) { + refresh(); + } + return timeBeforeExpiration(token.get()); + } + + @Override + public void refresh() { + // refresh should happen at once. Other calls wait for the refresh to finish and move on. + if (refreshLock.tryLock()) { + LOGGER.debug("Refreshing token"); + try { + latch.set(new CountDownLatch(1)); + refreshInProcess.set(true); + token.set(retrieveToken()); + LOGGER.debug("Token refreshed"); + } finally { + latch.get().countDown(); + refreshInProcess.set(false); + refreshLock.unlock(); + } + } else { + try { + LOGGER.debug("Waiting for token refresh to be finished"); + while (!refreshInProcess.get()) { + Thread.sleep(10); + } + latch.get().await(); + LOGGER.debug("Done waiting for token refresh"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + protected abstract T retrieveToken(); + + protected abstract String usernameFromToken(T token); + + protected abstract String passwordFromToken(T token); + + protected abstract Duration timeBeforeExpiration(T token); +} diff --git a/src/main/java/com/rabbitmq/client/impl/RpcContinuationRpcWrapper.java b/src/main/java/com/rabbitmq/client/impl/RpcContinuationRpcWrapper.java new file mode 100644 index 0000000000..b5f202237c --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/RpcContinuationRpcWrapper.java @@ -0,0 +1,45 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.ShutdownSignalException; + +/** + * + */ +public class RpcContinuationRpcWrapper implements RpcWrapper { + + private final AMQChannel.RpcContinuation continuation; + + public RpcContinuationRpcWrapper(AMQChannel.RpcContinuation continuation) { + this.continuation = continuation; + } + + @Override + public boolean canHandleReply(AMQCommand command) { + return continuation.canHandleReply(command); + } + + @Override + public void complete(AMQCommand command) { + continuation.handleCommand(command); + } + + @Override + public void shutdown(ShutdownSignalException signal) { + continuation.handleShutdownSignal(signal); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/RpcWrapper.java b/src/main/java/com/rabbitmq/client/impl/RpcWrapper.java new file mode 100644 index 0000000000..009ac45fe7 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/RpcWrapper.java @@ -0,0 +1,31 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.ShutdownSignalException; + +/** + * + */ +public interface RpcWrapper { + + boolean canHandleReply(AMQCommand command); + + void complete(AMQCommand command); + + void shutdown(ShutdownSignalException signal); + +} diff --git a/src/com/rabbitmq/client/impl/SetQueue.java b/src/main/java/com/rabbitmq/client/impl/SetQueue.java similarity index 71% rename from src/com/rabbitmq/client/impl/SetQueue.java rename to src/main/java/com/rabbitmq/client/impl/SetQueue.java index 6a02c66463..138cd0cfe7 100644 --- a/src/com/rabbitmq/client/impl/SetQueue.java +++ b/src/main/java/com/rabbitmq/client/impl/SetQueue.java @@ -1,3 +1,18 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.impl; import java.util.HashSet; @@ -5,13 +20,14 @@ import java.util.Queue; import java.util.Set; -/** A generic queue-like implementation (supporting operations addIfNotPresent, +/** + *

A generic queue-like implementation (supporting operations addIfNotPresent, * poll, contains, and isEmpty) * which restricts a queue element to appear at most once. * If the element is already present {@link #addIfNotPresent} returns false. - *

+ *

* Elements must not be null. - *

Concurrent Semantics
+ *

Concurrent Semantics

* This implementation is not thread-safe. * @param type of elements in the queue */ diff --git a/src/com/rabbitmq/client/impl/ShutdownNotifierComponent.java b/src/main/java/com/rabbitmq/client/impl/ShutdownNotifierComponent.java similarity index 77% rename from src/com/rabbitmq/client/impl/ShutdownNotifierComponent.java rename to src/main/java/com/rabbitmq/client/impl/ShutdownNotifierComponent.java index e48edf5ef5..dd79049c29 100644 --- a/src/com/rabbitmq/client/impl/ShutdownNotifierComponent.java +++ b/src/main/java/com/rabbitmq/client/impl/ShutdownNotifierComponent.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -44,6 +43,7 @@ public class ShutdownNotifierComponent implements ShutdownNotifier { */ private volatile ShutdownSignalException shutdownCause = null; + @Override public void addShutdownListener(ShutdownListener listener) { ShutdownSignalException sse = null; @@ -55,12 +55,14 @@ public void addShutdownListener(ShutdownListener listener) listener.shutdownCompleted(sse); } + @Override public ShutdownSignalException getCloseReason() { synchronized(this.monitor) { return this.shutdownCause; } } + @Override public void notifyListeners() { ShutdownSignalException sse = null; @@ -79,6 +81,7 @@ public void notifyListeners() } } + @Override public void removeShutdownListener(ShutdownListener listener) { synchronized(this.monitor) { @@ -86,6 +89,7 @@ public void removeShutdownListener(ShutdownListener listener) } } + @Override public boolean isOpen() { synchronized(this.monitor) { return this.shutdownCause == null; diff --git a/src/main/java/com/rabbitmq/client/impl/SocketFrameHandler.java b/src/main/java/com/rabbitmq/client/impl/SocketFrameHandler.java new file mode 100644 index 0000000000..0dd6d5cb74 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/SocketFrameHandler.java @@ -0,0 +1,247 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.AMQP; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSocket; +import java.io.*; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * A socket-based frame handler. + */ + +public class SocketFrameHandler implements FrameHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(SocketFrameHandler.class); + + /** The underlying socket */ + private final Socket _socket; + + /** + * Optional {@link ExecutorService} for final flush. + */ + private final ExecutorService _shutdownExecutor; + + /** Socket's inputstream - data from the broker - synchronized on */ + private final DataInputStream _inputStream; + private final Lock _inputStreamLock = new ReentrantLock(); + + /** Socket's outputstream - data to the broker - synchronized on */ + private final DataOutputStream _outputStream; + private final Lock _outputStreamLock = new ReentrantLock(); + + private final int maxInboundMessageBodySize; + + /** Time to linger before closing the socket forcefully. */ + public static final int SOCKET_CLOSING_TIMEOUT = 1; + + /** + * @param socket the socket to use + */ + public SocketFrameHandler(Socket socket) throws IOException { + this(socket, null, Integer.MAX_VALUE); + } + + /** + * @param socket the socket to use + */ + public SocketFrameHandler(Socket socket, ExecutorService shutdownExecutor, + int maxInboundMessageBodySize) throws IOException { + _socket = socket; + _shutdownExecutor = shutdownExecutor; + this.maxInboundMessageBodySize = maxInboundMessageBodySize; + + _inputStream = new DataInputStream(new BufferedInputStream(socket.getInputStream())); + _outputStream = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); + } + + @Override + public InetAddress getAddress() { + return _socket.getInetAddress(); + } + + @Override + public InetAddress getLocalAddress() { + return _socket.getLocalAddress(); + } + + // For testing only + public DataInputStream getInputStream() { + return _inputStream; + } + + @Override + public int getPort() { + return _socket.getPort(); + } + + @Override + public int getLocalPort() { + return _socket.getLocalPort(); + } + + @Override + public void setTimeout(int timeoutMs) + throws SocketException + { + _socket.setSoTimeout(timeoutMs); + } + + @Override + public int getTimeout() + throws SocketException + { + return _socket.getSoTimeout(); + } + + /** + * Write a 0-8-style connection header to the underlying socket, + * containing the specified version information, kickstarting the + * AMQP protocol version negotiation process. + * + * @param major major protocol version number + * @param minor minor protocol version number + * @throws IOException if there is a problem accessing the connection + * @see #sendHeader() + */ + public void sendHeader(int major, int minor) throws IOException { + _outputStreamLock.lock(); + try { + _outputStream.write("AMQP".getBytes("US-ASCII")); + _outputStream.write(1); + _outputStream.write(1); + _outputStream.write(major); + _outputStream.write(minor); + try { + _outputStream.flush(); + } catch (SSLHandshakeException e) { + LOGGER.error("TLS connection failed: {}", e.getMessage()); + throw e; + } + } finally { + _outputStreamLock.unlock(); + } + } + + /** + * Write a 0-9-1-style connection header to the underlying socket, + * containing the specified version information, kickstarting the + * AMQP protocol version negotiation process. + * + * @param major major protocol version number + * @param minor minor protocol version number + * @param revision protocol revision number + * @throws IOException if there is a problem accessing the connection + * @see #sendHeader() + */ + public void sendHeader(int major, int minor, int revision) throws IOException { + _outputStreamLock.lock(); + try { + _outputStream.write("AMQP".getBytes("US-ASCII")); + _outputStream.write(0); + _outputStream.write(major); + _outputStream.write(minor); + _outputStream.write(revision); + try { + _outputStream.flush(); + } catch (SSLHandshakeException e) { + LOGGER.error("TLS connection failed: {}", e.getMessage()); + throw e; + } + } finally { + _outputStreamLock.unlock(); + } + } + + @Override + public void sendHeader() throws IOException { + sendHeader(AMQP.PROTOCOL.MAJOR, AMQP.PROTOCOL.MINOR, AMQP.PROTOCOL.REVISION); + if (this._socket instanceof SSLSocket) { + TlsUtils.logPeerCertificateInfo(((SSLSocket) this._socket).getSession()); + } + } + + @Override + public void initialize(AMQConnection connection) { + connection.startMainLoop(); + } + + @Override + public Frame readFrame() throws IOException { + _inputStreamLock.lock(); + try { + return Frame.readFrom(_inputStream, this.maxInboundMessageBodySize); + } finally { + _inputStreamLock.unlock(); + } + } + + @Override + public void writeFrame(Frame frame) throws IOException { + _outputStreamLock.lock(); + try { + frame.writeTo(_outputStream); + } finally { + _outputStreamLock.unlock(); + } + } + + @Override + public void flush() throws IOException { + _outputStream.flush(); + } + + @Override + public void close() { + try { _socket.setSoLinger(true, SOCKET_CLOSING_TIMEOUT); } catch (Exception _e) {} + // async flush if possible + // see https://github.com/rabbitmq/rabbitmq-java-client/issues/194 + Callable flushCallable = new Callable() { + @Override + public Void call() throws Exception { + flush(); + return null; + } + }; + Future flushTask = null; + try { + if(this._shutdownExecutor == null) { + flushCallable.call(); + } else { + flushTask = this._shutdownExecutor.submit(flushCallable); + flushTask.get(SOCKET_CLOSING_TIMEOUT, TimeUnit.SECONDS); + } + } catch(Exception e) { + if(flushTask != null) { + flushTask.cancel(true); + } + } + try { _socket.close(); } catch (Exception _e) {} + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/SocketFrameHandlerFactory.java b/src/main/java/com/rabbitmq/client/impl/SocketFrameHandlerFactory.java new file mode 100644 index 0000000000..4aa083d060 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/SocketFrameHandlerFactory.java @@ -0,0 +1,91 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.Address; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.SocketConfigurator; +import com.rabbitmq.client.SslContextFactory; + +import javax.net.SocketFactory; +import java.io.IOException; +import java.net.Socket; +import java.util.concurrent.ExecutorService; + +public class SocketFrameHandlerFactory extends AbstractFrameHandlerFactory { + + private final SocketFactory socketFactory; + private final ExecutorService shutdownExecutor; + private final SslContextFactory sslContextFactory; + + public SocketFrameHandlerFactory(int connectionTimeout, SocketFactory socketFactory, SocketConfigurator configurator, + boolean ssl) { + this(connectionTimeout, socketFactory, configurator, ssl, null); + } + + public SocketFrameHandlerFactory(int connectionTimeout, SocketFactory socketFactory, SocketConfigurator configurator, + boolean ssl, ExecutorService shutdownExecutor) { + this(connectionTimeout, socketFactory, configurator, ssl, shutdownExecutor, null, + Integer.MAX_VALUE); + } + + public SocketFrameHandlerFactory(int connectionTimeout, SocketFactory socketFactory, SocketConfigurator configurator, + boolean ssl, ExecutorService shutdownExecutor, SslContextFactory sslContextFactory, + int maxInboundMessageBodySize) { + super(connectionTimeout, configurator, ssl, maxInboundMessageBodySize); + this.socketFactory = socketFactory; + this.shutdownExecutor = shutdownExecutor; + this.sslContextFactory = sslContextFactory; + } + + public FrameHandler create(Address addr, String connectionName) throws IOException { + int portNumber = ConnectionFactory.portOrDefault(addr.getPort(), ssl); + Socket socket = null; + try { + socket = createSocket(connectionName); + configurator.configure(socket); + + socket.connect(addr.toInetSocketAddress(portNumber), connectionTimeout); + return create(socket); + } catch (IOException ioe) { + quietTrySocketClose(socket); + throw ioe; + } + } + + protected Socket createSocket(String connectionName) throws IOException { + // SocketFactory takes precedence if specified + if (socketFactory != null) { + return socketFactory.createSocket(); + } else { + if (ssl) { + return sslContextFactory.create(connectionName).getSocketFactory().createSocket(); + } else { + return SocketFactory.getDefault().createSocket(); + } + } + } + + public FrameHandler create(Socket sock) throws IOException + { + return new SocketFrameHandler(sock, this.shutdownExecutor, this.maxInboundMessageBodySize); + } + + private static void quietTrySocketClose(Socket socket) { + if (socket != null) + try { socket.close(); } catch (Exception _e) {/*ignore exceptions*/} + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/StandardMetricsCollector.java b/src/main/java/com/rabbitmq/client/impl/StandardMetricsCollector.java new file mode 100644 index 0000000000..792231f5be --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/StandardMetricsCollector.java @@ -0,0 +1,190 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.MetricsCollector; + +/** + * Dropwizard Metrics implementation of {@link MetricsCollector}. + * Note transactions are not supported (see {@link MetricsCollector}. + * Metrics provides out-of-the-box support for report backends like JMX, + * Graphite, Ganglia, or plain HTTP. See Metrics documentation for + * more details. + * + * @see MetricsCollector + */ +public class StandardMetricsCollector extends AbstractMetricsCollector { + + private final MetricRegistry registry; + + private final Counter connections; + private final Counter channels; + private final Meter publishedMessages; + private final Meter consumedMessages; + private final Meter acknowledgedMessages; + private final Meter rejectedMessages; + private final Meter requeuedMessages; + private final Meter failedToPublishMessages; + private final Meter publishAcknowledgedMessages; + private final Meter publishNacknowledgedMessages; + private final Meter publishUnroutedMessages; + + public StandardMetricsCollector(MetricRegistry registry, String metricsPrefix) { + this.registry = registry; + this.connections = registry.counter(metricsPrefix+".connections"); + this.channels = registry.counter(metricsPrefix+".channels"); + this.publishedMessages = registry.meter(metricsPrefix+".published"); + this.failedToPublishMessages = registry.meter(metricsPrefix+".failed_to_publish"); + this.publishAcknowledgedMessages = registry.meter(metricsPrefix+".publish_ack"); + this.publishNacknowledgedMessages = registry.meter(metricsPrefix+".publish_nack"); + this.publishUnroutedMessages = registry.meter(metricsPrefix+".publish_unrouted"); + this.consumedMessages = registry.meter(metricsPrefix+".consumed"); + this.acknowledgedMessages = registry.meter(metricsPrefix+".acknowledged"); + this.rejectedMessages = registry.meter(metricsPrefix+".rejected"); + this.requeuedMessages = registry.meter(metricsPrefix+".requeued"); + } + + public StandardMetricsCollector() { + this(new MetricRegistry()); + } + + public StandardMetricsCollector(MetricRegistry metricRegistry) { + this(metricRegistry, "rabbitmq"); + } + + @Override + protected void incrementConnectionCount(Connection connection) { + connections.inc(); + } + + @Override + protected void decrementConnectionCount(Connection connection) { + connections.dec(); + } + + @Override + protected void incrementChannelCount(Channel channel) { + channels.inc(); + } + + @Override + protected void decrementChannelCount(Channel channel) { + channels.dec(); + } + + @Override + protected void markPublishedMessage() { + publishedMessages.mark(); + } + + @Override + protected void markMessagePublishFailed() { + failedToPublishMessages.mark(); + } + + @Override + protected void markConsumedMessage() { + consumedMessages.mark(); + } + + @Override + protected void markAcknowledgedMessage() { + acknowledgedMessages.mark(); + } + + @Override + @SuppressWarnings("deprecation") + protected void markRejectedMessage() { + + } + + @Override + protected void markRejectedMessage(boolean requeue) { + if (requeue) { + requeuedMessages.mark(); + } + rejectedMessages.mark(); + } + + @Override + protected void markMessagePublishAcknowledged() { + publishAcknowledgedMessages.mark(); + } + + @Override + protected void markMessagePublishNotAcknowledged() { + publishNacknowledgedMessages.mark(); + } + + @Override + protected void markPublishedMessageUnrouted() { + publishUnroutedMessages.mark(); + } + + public MetricRegistry getMetricRegistry() { + return registry; + } + + public Counter getConnections() { + return connections; + } + + public Counter getChannels() { + return channels; + } + + public Meter getPublishedMessages() { + return publishedMessages; + } + + public Meter getConsumedMessages() { + return consumedMessages; + } + + public Meter getAcknowledgedMessages() { + return acknowledgedMessages; + } + + public Meter getRejectedMessages() { + return rejectedMessages; + } + + public Meter getRequeuedMessages() { + return this.requeuedMessages; + } + + public Meter getFailedToPublishMessages() { + return failedToPublishMessages; + } + + public Meter getPublishAcknowledgedMessages() { + return publishAcknowledgedMessages; + } + + public Meter getPublishNotAcknowledgedMessages() { + return publishNacknowledgedMessages; + } + + public Meter getPublishUnroutedMessages() { + return publishUnroutedMessages; + } + +} diff --git a/src/main/java/com/rabbitmq/client/impl/StrictExceptionHandler.java b/src/main/java/com/rabbitmq/client/impl/StrictExceptionHandler.java new file mode 100644 index 0000000000..403a691e83 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/StrictExceptionHandler.java @@ -0,0 +1,85 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.*; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +/** + * An implementation of {@link com.rabbitmq.client.ExceptionHandler} that does + * close channels on unhandled consumer exception. + * + * Used by {@link AMQConnection}. + * + * @see ExceptionHandler + * @see com.rabbitmq.client.ConnectionFactory#setExceptionHandler(ExceptionHandler) + */ +public class StrictExceptionHandler extends ForgivingExceptionHandler implements ExceptionHandler { + @Override + public void handleReturnListenerException(Channel channel, Throwable exception) { + handleChannelKiller(channel, exception, "ReturnListener.handleReturn"); + } + + @Override + public void handleConfirmListenerException(Channel channel, Throwable exception) { + handleChannelKiller(channel, exception, "ConfirmListener.handle{N,A}ck"); + } + + @Override + public void handleBlockedListenerException(Connection connection, Throwable exception) { + handleConnectionKiller(connection, exception, "BlockedListener"); + } + + @Override + public void handleConsumerException(Channel channel, Throwable exception, + Consumer consumer, String consumerTag, + String methodName) + { + String logMessage = "Consumer " + consumer + + " (" + consumerTag + ")" + + " method " + methodName + + " for channel " + channel; + String closeMessage = "Consumer" + + " (" + consumerTag + ")" + + " method " + methodName + + " for channel " + channel; + handleChannelKiller(channel, exception, logMessage, closeMessage); + } + + @Override + protected void handleChannelKiller(Channel channel, Throwable exception, String what) { + handleChannelKiller(channel, exception, what, what); + } + + protected void handleChannelKiller(Channel channel, Throwable exception, String logMessage, String closeMessage) { + log(logMessage + " threw an exception for channel " + channel, exception); + try { + channel.close(AMQP.REPLY_SUCCESS, "Closed due to exception from " + closeMessage); + } catch (AlreadyClosedException ace) { + // noop + } catch (TimeoutException ace) { + // noop + } catch (IOException ioe) { + log("Failure during close of channel " + channel + " after " + exception, ioe); + channel.getConnection().abort(AMQP.INTERNAL_ERROR, "Internal error closing channel for " + closeMessage); + } + } + + + +} diff --git a/src/main/java/com/rabbitmq/client/impl/TlsUtils.java b/src/main/java/com/rabbitmq/client/impl/TlsUtils.java new file mode 100644 index 0000000000..45a5ecfde5 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/TlsUtils.java @@ -0,0 +1,237 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLSession; +import java.security.cert.Certificate; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +/** + * Utility to extract information from X509 certificates. + * + * @since 5.7.0 + */ +public class TlsUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(TlsUtils.class); + private static final List KEY_USAGE = Collections.unmodifiableList(Arrays.asList( + "digitalSignature", "nonRepudiation", "keyEncipherment", + "dataEncipherment", "keyAgreement", "keyCertSign", + "cRLSign", "encipherOnly", "decipherOnly" + )); + private static final Map EXTENDED_KEY_USAGE = Collections.unmodifiableMap(new HashMap() {{ + put("1.3.6.1.5.5.7.3.1", "TLS Web server authentication"); + put("1.3.6.1.5.5.7.3.2", "TLS Web client authentication"); + put("1.3.6.1.5.5.7.3.3", "Signing of downloadable executable code"); + put("1.3.6.1.5.5.7.3.4", "E-mail protection"); + put("1.3.6.1.5.5.7.3.8", "Binding the hash of an object to a time from an agreed-upon time"); + }}); + private static String PARSING_ERROR = ""; + private static final Map> EXTENSIONS = Collections.unmodifiableMap( + new HashMap>() {{ + put("2.5.29.14", (v, c) -> "SubjectKeyIdentifier = " + octetStringHexDump(v)); + put("2.5.29.15", (v, c) -> "KeyUsage = " + keyUsageBitString(c.getKeyUsage(), v)); + put("2.5.29.16", (v, c) -> "PrivateKeyUsage = " + hexDump(0, v)); + put("2.5.29.17", (v, c) -> { + try { + return "SubjectAlternativeName = " + sans(c, "/"); + } catch (CertificateParsingException e) { + return "SubjectAlternativeName = " + PARSING_ERROR; + } + }); + put("2.5.29.18", (v, c) -> "IssuerAlternativeName = " + hexDump(0, v)); + put("2.5.29.19", (v, c) -> "BasicConstraints = " + basicConstraints(v)); + put("2.5.29.30", (v, c) -> "NameConstraints = " + hexDump(0, v)); + put("2.5.29.33", (v, c) -> "PolicyMappings = " + hexDump(0, v)); + put("2.5.29.35", (v, c) -> "AuthorityKeyIdentifier = " + authorityKeyIdentifier(v)); + put("2.5.29.36", (v, c) -> "PolicyConstraints = " + hexDump(0, v)); + put("2.5.29.37", (v, c) -> "ExtendedKeyUsage = " + extendedKeyUsage(v, c)); + }}); + + /** + * Log details on peer certificate and certification chain. + *

+ * The log level is debug. Common X509 extensions are displayed in a best-effort + * fashion, a hexadecimal dump is made for less commonly used extensions. + * + * @param session the {@link SSLSession} to extract the certificates from + */ + public static void logPeerCertificateInfo(SSLSession session) { + if (LOGGER.isDebugEnabled()) { + try { + Certificate[] peerCertificates = session.getPeerCertificates(); + if (peerCertificates != null && peerCertificates.length > 0) { + LOGGER.debug(peerCertificateInfo(peerCertificates[0], "Peer's leaf certificate")); + for (int i = 1; i < peerCertificates.length; i++) { + LOGGER.debug(peerCertificateInfo(peerCertificates[i], "Peer's certificate chain entry")); + } + } + } catch (Exception e) { + LOGGER.debug("Error while logging peer certificate info: {}", e.getMessage()); + } + } + } + + /** + * Get a string representation of certificate info. + * + * @param certificate the certificate to analyze + * @param prefix the line prefix + * @return information about the certificate + */ + public static String peerCertificateInfo(Certificate certificate, String prefix) { + X509Certificate c = (X509Certificate) certificate; + try { + return String.format("%s subject: %s, subject alternative names: %s, " + + "issuer: %s, not valid after: %s, X.509 usage extensions: %s", + stripCRLF(prefix), stripCRLF(c.getSubjectX500Principal().getName()), stripCRLF(sans(c, ",")), stripCRLF(c.getIssuerX500Principal().getName()), + c.getNotAfter(), stripCRLF(extensions(c))); + } catch (Exception e) { + return "Error while retrieving " + prefix + " certificate information"; + } + } + + private static String sans(X509Certificate c, String separator) throws CertificateParsingException { + return String.join(separator, Optional.ofNullable(c.getSubjectAlternativeNames()) + .orElse(new ArrayList<>()) + .stream() + .map(v -> v.toString()) + .collect(Collectors.toList())); + } + + /** + * Human-readable representation of an X509 certificate extension. + *

+ * Common extensions are supported in a best-effort fashion, less commonly + * used extensions are displayed as an hexadecimal dump. + *

+ * Extensions come encoded as a DER Octet String, which itself can contain + * other DER-encoded objects, making a comprehensive support in this utility + * impossible. + * + * @param oid extension OID + * @param derOctetString the extension value as a DER octet string + * @param certificate the certificate + * @return the OID and the value + * @see A Layman's Guide to a Subset of ASN.1, BER, and DER + * @see DER Encoding of ASN.1 Types + */ + public static String extensionPrettyPrint(String oid, byte[] derOctetString, X509Certificate certificate) { + try { + return EXTENSIONS.getOrDefault(oid, (v, c) -> oid + " = " + hexDump(0, derOctetString)) + .apply(derOctetString, certificate); + } catch (Exception e) { + return oid + " = " + PARSING_ERROR; + } + } + + /** + * Strips carriage return (CR) and line feed (LF) characters to mitigate CWE-117. + * @return sanitised string value + */ + public static String stripCRLF(String value) { + return value.replaceAll("\r", "").replaceAll("\n", ""); + } + + private static String extensions(X509Certificate certificate) { + List extensions = new ArrayList<>(); + for (String oid : certificate.getCriticalExtensionOIDs()) { + extensions.add(extensionPrettyPrint(oid, certificate.getExtensionValue(oid), certificate) + " (critical)"); + } + for (String oid : certificate.getNonCriticalExtensionOIDs()) { + extensions.add(extensionPrettyPrint(oid, certificate.getExtensionValue(oid), certificate) + " (non-critical)"); + } + return String.join(", ", extensions); + } + + private static String octetStringHexDump(byte[] derOctetString) { + // this is an octet string in a octet string, [4 total_length 4 length ...] + if (derOctetString.length > 4 && derOctetString[0] == 4 && derOctetString[2] == 4) { + return hexDump(4, derOctetString); + } else { + return hexDump(0, derOctetString); + } + } + + private static String hexDump(int start, byte[] derOctetString) { + List hexs = new ArrayList<>(); + for (int i = start; i < derOctetString.length; i++) { + hexs.add(String.format("%02X", derOctetString[i])); + } + return String.join(":", hexs); + } + + private static String keyUsageBitString(boolean[] keyUsage, byte[] derOctetString) { + if (keyUsage != null) { + List usage = new ArrayList<>(); + for (int i = 0; i < keyUsage.length; i++) { + if (keyUsage[i]) { + usage.add(KEY_USAGE.get(i)); + } + } + return String.join("/", usage); + } else { + return hexDump(0, derOctetString); + } + } + + private static String basicConstraints(byte[] derOctetString) { + if (derOctetString.length == 4 && derOctetString[3] == 0) { + // e.g. 04:02:30:00 [octet_string length sequence size] + return "CA:FALSE"; + } else if (derOctetString.length >= 7 && derOctetString[2] == 48 && derOctetString[4] == 1) { + // e.g. 04:05:30:03:01:01:FF [octet_string length sequence boolean length boolean_value] + return "CA:" + (derOctetString[6] == 0 ? "FALSE" : "TRUE"); + } else { + return hexDump(0, derOctetString); + } + } + + private static String authorityKeyIdentifier(byte[] derOctetString) { + if (derOctetString.length == 26 && derOctetString[0] == 04) { + // e.g. 04:18:30:16:80:14:FB:D2:7C:63:DF:7F:D4:A4:8E:9A:20:43:F5:DC:75:6F:B6:D8:51:6F + // [octet_string length sequence ?? ?? key_length key] + return "keyid:" + hexDump(6, derOctetString); + } else { + return hexDump(0, derOctetString); + } + + } + + private static String extendedKeyUsage(byte[] derOctetString, X509Certificate certificate) { + List extendedKeyUsage = null; + try { + extendedKeyUsage = certificate.getExtendedKeyUsage(); + if (extendedKeyUsage == null) { + return hexDump(0, derOctetString); + } else { + return String.join("/", extendedKeyUsage.stream() + .map(oid -> EXTENDED_KEY_USAGE.getOrDefault(oid, oid)) + .collect(Collectors.toList())); + } + } catch (CertificateParsingException e) { + return PARSING_ERROR; + } + } + +} diff --git a/src/com/rabbitmq/client/impl/TruncatedInputStream.java b/src/main/java/com/rabbitmq/client/impl/TruncatedInputStream.java similarity index 51% rename from src/com/rabbitmq/client/impl/TruncatedInputStream.java rename to src/main/java/com/rabbitmq/client/impl/TruncatedInputStream.java index 767ffa235d..ea548c494a 100644 --- a/src/com/rabbitmq/client/impl/TruncatedInputStream.java +++ b/src/main/java/com/rabbitmq/client/impl/TruncatedInputStream.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -37,16 +36,19 @@ public TruncatedInputStream(InputStream in, long limit) { this.limit = limit; } - @Override public int available() throws IOException { + @Override + public int available() throws IOException { return (int) Math.min(limit - counter, super.available()); } - @Override public void mark(int readlimit) { + @Override + public synchronized void mark(int readlimit) { super.mark(readlimit); mark = counter; } - @Override public int read() throws IOException { + @Override + public int read() throws IOException { if (counter < limit) { int result = super.read(); if (result >= 0) @@ -56,7 +58,8 @@ public TruncatedInputStream(InputStream in, long limit) { return -1; } - @Override public int read(byte[] b, int off, int len) throws IOException { + @Override + public int read(byte[] b, int off, int len) throws IOException { if (limit > counter) { int result = super.read(b, off, (int) Math.min(len, limit - counter)); @@ -67,12 +70,14 @@ public TruncatedInputStream(InputStream in, long limit) { return -1; } - @Override public void reset() throws IOException { + @Override + public synchronized void reset() throws IOException { super.reset(); counter = mark; } - @Override public long skip(long n) throws IOException { + @Override + public long skip(long n) throws IOException { long result = super.skip(Math.min(n, limit - counter)); counter += result; return result; diff --git a/src/main/java/com/rabbitmq/client/impl/UnknownChannelException.java b/src/main/java/com/rabbitmq/client/impl/UnknownChannelException.java new file mode 100644 index 0000000000..ea12d943fd --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/UnknownChannelException.java @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.impl; + +class UnknownChannelException extends RuntimeException { + /** Default for non-checking. */ + private static final long serialVersionUID = 1L; + private final int channelNumber; + + public UnknownChannelException(int channelNumber) { + super("Unknown channel number " + channelNumber); + this.channelNumber = channelNumber; + } + + public int getChannelNumber() { + return channelNumber; + } + +} diff --git a/src/main/java/com/rabbitmq/client/impl/UpdateSecretExtension.java b/src/main/java/com/rabbitmq/client/impl/UpdateSecretExtension.java new file mode 100644 index 0000000000..ff5e7bfeed --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/UpdateSecretExtension.java @@ -0,0 +1,108 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.LongString; + +import java.io.IOException; +import java.util.Objects; + +/** + * Helper for update-secret extension {@link com.rabbitmq.client.Method}. + *

+ * {@link com.rabbitmq.client.Method} classes are usually automatically + * generated, but providing the class directly is necessary in this case + * for some internal CI testing jobs running against RabbitMQ 3.7. + * + * @since 5.8.0 + */ +abstract class UpdateSecretExtension { + + static class UpdateSecret extends Method { + + private final LongString newSecret; + private final String reason; + + public UpdateSecret(LongString newSecret, String reason) { + if (newSecret == null) + throw new IllegalStateException("Invalid configuration: 'newSecret' must be non-null."); + if (reason == null) + throw new IllegalStateException("Invalid configuration: 'reason' must be non-null."); + this.newSecret = newSecret; + this.reason = reason; + } + + public String getReason() { + return reason; + } + + public int protocolClassId() { + return 10; + } + + public int protocolMethodId() { + return 70; + } + + public String protocolMethodName() { + return "connection.update-secret"; + } + + public boolean hasContent() { + return false; + } + + public Object visit(AMQImpl.MethodVisitor visitor) throws IOException { + return null; + } + + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + UpdateSecret that = (UpdateSecret) o; + if (!Objects.equals(newSecret, that.newSecret)) + return false; + return Objects.equals(reason, that.reason); + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + (newSecret != null ? newSecret.hashCode() : 0); + result = 31 * result + (reason != null ? reason.hashCode() : 0); + return result; + } + + public void appendArgumentDebugStringTo(StringBuilder acc) { + acc.append("(new-secret=") + .append(this.newSecret) + .append(", reason=") + .append(this.reason) + .append(")"); + } + + public void writeArgumentsTo(MethodArgumentWriter writer) + throws IOException { + writer.writeLongstr(this.newSecret); + writer.writeShortstr(this.reason); + } + } +} + diff --git a/src/main/java/com/rabbitmq/client/impl/Utils.java b/src/main/java/com/rabbitmq/client/impl/Utils.java new file mode 100644 index 0000000000..6d1fb8ec39 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/Utils.java @@ -0,0 +1,31 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +final class Utils { + + private static final int AVAILABLE_PROCESSORS = + Integer.parseInt( + System.getProperty( + "rabbitmq.amqp.client.availableProcessors", + String.valueOf(Runtime.getRuntime().availableProcessors()))); + + static int availableProcessors() { + return AVAILABLE_PROCESSORS; + } + + private Utils() {} +} diff --git a/src/com/rabbitmq/client/impl/ValueReader.java b/src/main/java/com/rabbitmq/client/impl/ValueReader.java similarity index 80% rename from src/com/rabbitmq/client/impl/ValueReader.java rename to src/main/java/com/rabbitmq/client/impl/ValueReader.java index 7f171a63eb..0f89cfbc7c 100644 --- a/src/com/rabbitmq/client/impl/ValueReader.java +++ b/src/main/java/com/rabbitmq/client/impl/ValueReader.java @@ -1,23 +1,23 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; import java.io.DataInputStream; +import java.io.EOFException; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; @@ -154,7 +154,8 @@ private static Map readTable(DataInputStream in) return table; } - private static Object readFieldValue(DataInputStream in) + // package protected for testing + static Object readFieldValue(DataInputStream in) throws IOException { Object value = null; switch(in.readUnsignedByte()) { @@ -164,6 +165,9 @@ private static Object readFieldValue(DataInputStream in) case 'I': value = in.readInt(); break; + case 'i': + value = readUnsignedInt(in); + break; case 'D': int scale = in.readUnsignedByte(); byte [] unscaled = new byte[4]; @@ -182,6 +186,9 @@ private static Object readFieldValue(DataInputStream in) case 'b': value = in.readByte(); break; + case 'B': + value = in.readUnsignedByte(); + break; case 'd': value = in.readDouble(); break; @@ -194,6 +201,9 @@ private static Object readFieldValue(DataInputStream in) case 's': value = in.readShort(); break; + case 'u': + value = in.readUnsignedShort(); + break; case 't': value = in.readBoolean(); break; @@ -210,6 +220,19 @@ private static Object readFieldValue(DataInputStream in) return value; } + /** Read an unsigned int */ + private static long readUnsignedInt(DataInputStream in) + throws IOException + { + long ch1 = in.read(); + long ch2 = in.read(); + long ch3 = in.read(); + long ch4 = in.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) + throw new EOFException(); + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4); + } + /** Read a field-array */ private static List readArray(DataInputStream in) throws IOException diff --git a/src/com/rabbitmq/client/impl/ValueWriter.java b/src/main/java/com/rabbitmq/client/impl/ValueWriter.java similarity index 84% rename from src/com/rabbitmq/client/impl/ValueWriter.java rename to src/main/java/com/rabbitmq/client/impl/ValueWriter.java index 05e3e0a723..79a1936fd3 100644 --- a/src/com/rabbitmq/client/impl/ValueWriter.java +++ b/src/main/java/com/rabbitmq/client/impl/ValueWriter.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -144,9 +143,17 @@ else if(value instanceof Integer) { else if(value instanceof BigDecimal) { writeOctet('D'); BigDecimal decimal = (BigDecimal)value; + // The scale must be an unsigned octet, therefore its values must + // be between 0 and 255 + if(decimal.scale() > 255 || decimal.scale() < 0) + throw new IllegalArgumentException + ("BigDecimal has too large of a scale to be encoded. " + + "The scale was: " + decimal.scale()); writeOctet(decimal.scale()); BigInteger unscaled = decimal.unscaledValue(); - if(unscaled.bitLength() > 32) /*Integer.SIZE in Java 1.5*/ + // We use 31 instead of 32 (Integer.SIZE) because bitLength ignores the sign bit, + // so e.g. new BigDecimal(Integer.MAX_VALUE) comes out to 31 bits. + if(unscaled.bitLength() > 31) throw new IllegalArgumentException ("BigDecimal too large to be encoded"); writeLong(decimal.unscaledValue().intValue()); diff --git a/src/com/rabbitmq/client/impl/VariableLinkedBlockingQueue.java b/src/main/java/com/rabbitmq/client/impl/VariableLinkedBlockingQueue.java similarity index 94% rename from src/com/rabbitmq/client/impl/VariableLinkedBlockingQueue.java rename to src/main/java/com/rabbitmq/client/impl/VariableLinkedBlockingQueue.java index 7520e20abc..7831257a1a 100644 --- a/src/com/rabbitmq/client/impl/VariableLinkedBlockingQueue.java +++ b/src/main/java/com/rabbitmq/client/impl/VariableLinkedBlockingQueue.java @@ -1,12 +1,28 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + /* - * Modifications Copyright 2014 GoPivotal, Inc and licenced as per - * the rest of the RabbitMQ Java client. + * Modifications Copyright 2015-2023 Broadcom. All Rights Reserved. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * Licenced as per the rest of the RabbitMQ Java client. */ /* * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/licenses/publicdomain + * https://creativecommons.org/licenses/publicdomain */ package com.rabbitmq.client.impl; @@ -214,13 +230,14 @@ public VariableLinkedBlockingQueue(Collection c) { * * @return the number of elements in this queue. */ + @Override public int size() { return count.get(); } /** * Set a new capacity for the queue. Increasing the capacity can - * cause any waiting {@link #put(E)} invocations to succeed if the new + * cause any waiting {@link #put(Object)} invocations to succeed if the new * capacity is larger than the queue. * @param capacity the new capacity for the queue */ @@ -246,6 +263,7 @@ public void setCapacity(int capacity) { * case that a waiting consumer is ready to take an * element out of an otherwise full queue. */ + @Override public int remainingCapacity() { return capacity - count.get(); } @@ -257,6 +275,7 @@ public int remainingCapacity() { * @throws InterruptedException if interrupted while waiting. * @throws NullPointerException if the specified element is null. */ + @Override public void put(E o) throws InterruptedException { if (o == null) throw new NullPointerException(); // Note: convention in all put/take/etc is to preset @@ -306,6 +325,7 @@ public void put(E o) throws InterruptedException { * @throws InterruptedException if interrupted while waiting. * @throws NullPointerException if the specified element is null. */ + @Override public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException { @@ -350,6 +370,7 @@ public boolean offer(E o, long timeout, TimeUnit unit) * this queue, else false * @throws NullPointerException if the specified element is null */ + @Override public boolean offer(E o) { if (o == null) throw new NullPointerException(); final AtomicInteger count = this.count; @@ -374,6 +395,7 @@ public boolean offer(E o) { } + @Override public E take() throws InterruptedException { E x; int c = -1; @@ -401,6 +423,7 @@ public E take() throws InterruptedException { return x; } + @Override public E poll(long timeout, TimeUnit unit) throws InterruptedException { E x = null; int c = -1; @@ -434,6 +457,7 @@ public E poll(long timeout, TimeUnit unit) throws InterruptedException { return x; } + @Override public E poll() { final AtomicInteger count = this.count; if (count.get() == 0) @@ -458,6 +482,7 @@ public E poll() { } + @Override public E peek() { if (count.get() == 0) return null; @@ -474,6 +499,7 @@ public E peek() { } } + @Override public boolean remove(Object o) { if (o == null) return false; boolean removed = false; @@ -501,6 +527,7 @@ public boolean remove(Object o) { return removed; } + @Override public Object[] toArray() { fullyLock(); try { @@ -515,6 +542,7 @@ public Object[] toArray() { } } + @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { fullyLock(); @@ -525,7 +553,7 @@ public T[] toArray(T[] a) { (a.getClass().getComponentType(), size); int k = 0; - for (Node p = head.next; p != null; p = p.next) + for (Node p = head.next; p != null; p = p.next) a[k++] = (T)p.item; return a; } finally { @@ -533,6 +561,7 @@ public T[] toArray(T[] a) { } } + @Override public String toString() { fullyLock(); try { @@ -542,6 +571,7 @@ public String toString() { } } + @Override public void clear() { fullyLock(); try { @@ -553,6 +583,7 @@ public void clear() { } } + @Override public int drainTo(Collection c) { if (c == null) throw new NullPointerException(); @@ -578,6 +609,7 @@ public int drainTo(Collection c) { return n; } + @Override public int drainTo(Collection c, int maxElements) { if (c == null) throw new NullPointerException(); @@ -616,6 +648,7 @@ public int drainTo(Collection c, int maxElements) { * * @return an iterator over the elements in this queue in proper sequence. */ + @Override public Iterator iterator() { return new Itr(); } @@ -645,10 +678,12 @@ private class Itr implements Iterator { } } + @Override public boolean hasNext() { return current != null; } + @Override public E next() { final ReentrantLock putLock = VariableLinkedBlockingQueue.this.putLock; final ReentrantLock takeLock = VariableLinkedBlockingQueue.this.takeLock; @@ -669,6 +704,7 @@ public E next() { } } + @Override public void remove() { if (lastRet == null) throw new IllegalStateException(); diff --git a/src/com/rabbitmq/client/impl/Version.java b/src/main/java/com/rabbitmq/client/impl/Version.java similarity index 73% rename from src/com/rabbitmq/client/impl/Version.java rename to src/main/java/com/rabbitmq/client/impl/Version.java index 3735e678ed..cc313142b4 100644 --- a/src/com/rabbitmq/client/impl/Version.java +++ b/src/main/java/com/rabbitmq/client/impl/Version.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.impl; @@ -54,7 +53,7 @@ public int getMinor() { /** * Retrieve a String representation of the version in the standard - * AMQP version format of - + * AMQP version format of major-minor. * * @return a String representation of the version * @see Object#toString() diff --git a/src/com/rabbitmq/client/impl/WorkPool.java b/src/main/java/com/rabbitmq/client/impl/WorkPool.java similarity index 79% rename from src/com/rabbitmq/client/impl/WorkPool.java rename to src/main/java/com/rabbitmq/client/impl/WorkPool.java index 2251fdea86..c9f93d0d3f 100644 --- a/src/com/rabbitmq/client/impl/WorkPool.java +++ b/src/main/java/com/rabbitmq/client/impl/WorkPool.java @@ -1,3 +1,18 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.impl; import java.util.Collection; @@ -6,62 +21,34 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; /** - * This is a generic implementation of the Channels specification + *

This is a generic implementation of the channels specification * in Channeling Work, Nov 2010 (channels.pdf). - *

* Objects of type K must be registered, with registerKey(K), * and then they become clients and a queue of * items (type W) is stored for each client. - *

+ *

* Each client has a state which is exactly one of dormant, * in progress or ready. Immediately after registration a client is dormant. - *

- * Items may be (singly) added to (the end of) a client's queue with addWorkItem(K,W). + * Items may be (singly) added to (the end of) a client's queue with {@link WorkPool#addWorkItem(Object, Object)}. * If the client is dormant it becomes ready thereby. All other states remain unchanged. - *

* The next ready client, together with a collection of its items, * may be retrieved with nextWorkBlock(collection,max) * (making that client in progress). - *

* An in progress client can finish (processing a batch of items) with finishWorkBlock(K). * It then becomes either dormant or ready, depending if its queue of work items is empty or no. - *

* If a client has items queued, it is either in progress or ready but cannot be both. * When work is finished it may be marked ready if there is further work, * or dormant if there is not. * There is never any work for a dormant client. - *

* A client may be unregistered, with unregisterKey(K), which removes the client from * all parts of the state, and any queue of items stored with it. * All clients may be unregistered with unregisterAllKeys(). - *

- * Concurrent Semantics
+ *

Concurrent Semantics

* This implementation is thread-safe. - *

- * Implementation Notes
- * The state is, roughly, as follows: - *

 pool :: map(K, seq W)
- * inProgress :: set K
- * ready :: iseq K
- *

- * where a seq is a sequence (queue or list) and an iseq - * (i for injective) is a sequence with no duplicates. - *

- * State transitions

- *      finish(k)            -------------
- *             -----------> | (dormant)   |
- *            |              -------------
- *  -------------  next()        | add(item)
- * | in progress | <---------    |
- *  -------------            |   V
- *            |              -------------
- *             -----------> | ready       |
- *      finish(k)            -------------
- * 
- * dormant is not represented in the implementation state, and adding items - * when the client is in progress or ready does not change its state. * @param Key -- type of client * @param Work -- type of work item */ @@ -76,11 +63,34 @@ public class WorkPool { private final Map> pool = new HashMap>(); /** Those keys which want limits to be removed. We do not limit queue size if this is non-empty. */ private final Set unlimited = new HashSet(); + private final BiConsumer, W> enqueueingCallback; + + public WorkPool(final int queueingTimeout) { + if (queueingTimeout > 0) { + this.enqueueingCallback = (queue, item) -> { + try { + boolean offered = queue.offer(item, queueingTimeout, TimeUnit.MILLISECONDS); + if (!offered) { + throw new WorkPoolFullException("Could not enqueue in work pool after " + queueingTimeout + " ms."); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }; + } else { + this.enqueueingCallback = (queue, item) -> { + try { + queue.put(item); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }; + } + } /** * Add client key to pool of item queues, with an empty queue. * A client is initially dormant. - *

* No-op if key already present. * @param key client to add to pool */ @@ -123,6 +133,7 @@ public void unregisterKey(K key) { this.pool.remove(key); this.ready.remove(key); this.inProgress.remove(key); + this.unlimited.remove(key); } } @@ -134,6 +145,7 @@ public void unregisterAllKeys() { this.pool.clear(); this.ready.clear(); this.inProgress.clear(); + this.unlimited.clear(); } } @@ -141,7 +153,6 @@ public void unregisterAllKeys() { * Return the next ready client, * and transfer a collection of that client's items to process. * Mark client in progress. - *

* If there is no ready client, return null. * @param to collection object in which to transfer items * @param size max number of items to transfer @@ -193,11 +204,7 @@ public boolean addWorkItem(K key, W item) { } // The put operation may block. We need to make sure we are not holding the lock while that happens. if (queue != null) { - try { - queue.put(item); - } catch (InterruptedException e) { - // ok - } + enqueueingCallback.accept(queue, item); synchronized (this) { if (isDormant(key)) { @@ -258,4 +265,5 @@ private K readyToInProgress() { } return key; } + } diff --git a/src/main/java/com/rabbitmq/client/impl/WorkPoolFullException.java b/src/main/java/com/rabbitmq/client/impl/WorkPoolFullException.java new file mode 100644 index 0000000000..8b753b747d --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/WorkPoolFullException.java @@ -0,0 +1,26 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +/** + * Exception thrown when {@link WorkPool} enqueueing times out. + */ +public class WorkPoolFullException extends RuntimeException { + + public WorkPoolFullException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/BlockingQueueNioQueue.java b/src/main/java/com/rabbitmq/client/impl/nio/BlockingQueueNioQueue.java new file mode 100644 index 0000000000..9fd90d9005 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/BlockingQueueNioQueue.java @@ -0,0 +1,41 @@ +package com.rabbitmq.client.impl.nio; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * Bridge between {@link NioQueue} and JDK's {@link BlockingQueue}. + * + * @see NioQueue + * @since 5.5.0 + */ +public class BlockingQueueNioQueue implements NioQueue { + + private final BlockingQueue delegate; + private final int writeEnqueuingTimeoutInMs; + + public BlockingQueueNioQueue(BlockingQueue delegate, int writeEnqueuingTimeoutInMs) { + this.delegate = delegate; + this.writeEnqueuingTimeoutInMs = writeEnqueuingTimeoutInMs; + } + + @Override + public boolean offer(WriteRequest writeRequest) throws InterruptedException { + return this.delegate.offer(writeRequest, writeEnqueuingTimeoutInMs, TimeUnit.MILLISECONDS); + } + + @Override + public int size() { + return this.delegate.size(); + } + + @Override + public WriteRequest poll() { + return this.delegate.poll(); + } + + @Override + public boolean isEmpty() { + return this.delegate.isEmpty(); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/ByteBufferFactory.java b/src/main/java/com/rabbitmq/client/impl/nio/ByteBufferFactory.java new file mode 100644 index 0000000000..984b5482c9 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/ByteBufferFactory.java @@ -0,0 +1,57 @@ +package com.rabbitmq.client.impl.nio; + +import java.nio.ByteBuffer; + +/** + * Contract to create {@link ByteBuffer}s. + * + * @see NioParams + * @since 5.5.0 + */ +public interface ByteBufferFactory { + + /** + * Create the {@link ByteBuffer} that contains inbound frames. + * This buffer is the network buffer for plain connections. + * When using SSL/TLS, this buffer isn't directly connected to + * the network, the encrypted read buffer is. + * + * @param nioContext + * @return + */ + ByteBuffer createReadBuffer(NioContext nioContext); + + /** + * Create the {@link ByteBuffer} that contains outbound frames. + * This buffer is the network buffer for plain connections. + * When using SSL/TLS, this buffer isn't directed connected to + * the network, the encrypted write buffer is. + * + * @param nioContext + * @return + */ + ByteBuffer createWriteBuffer(NioContext nioContext); + + /** + * Create the network read {@link ByteBuffer}. + * This buffer contains encrypted frames read from the network. + * The {@link javax.net.ssl.SSLEngine} decrypts frame and pass them + * over to the read buffer. + * + * @param nioContext + * @return + */ + ByteBuffer createEncryptedReadBuffer(NioContext nioContext); + + /** + * Create the network write {@link ByteBuffer}. + * This buffer contains encrypted outbound frames. These + * frames come from the write buffer that sends them through + * the {@link javax.net.ssl.SSLContext} for encryption to + * this buffer. + * + * @param nioContext + * @return + */ + ByteBuffer createEncryptedWriteBuffer(NioContext nioContext); +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/ByteBufferOutputStream.java b/src/main/java/com/rabbitmq/client/impl/nio/ByteBufferOutputStream.java new file mode 100644 index 0000000000..8e69cebb22 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/ByteBufferOutputStream.java @@ -0,0 +1,56 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; + +/** + * Bridge between the byte buffer and stream worlds. + */ +public class ByteBufferOutputStream extends OutputStream { + + private final WritableByteChannel channel; + + private final ByteBuffer buffer; + + public ByteBufferOutputStream(WritableByteChannel channel, ByteBuffer buffer) { + this.buffer = buffer; + this.channel = channel; + } + + @Override + public void write(int b) throws IOException { + if(!buffer.hasRemaining()) { + drain(channel, buffer); + } + buffer.put((byte) b); + } + + @Override + public void flush() throws IOException { + drain(channel, buffer); + } + + public static void drain(WritableByteChannel channel, ByteBuffer buffer) throws IOException { + buffer.flip(); + while(buffer.hasRemaining() && channel.write(buffer) != -1); + buffer.clear(); + } + +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/DefaultByteBufferFactory.java b/src/main/java/com/rabbitmq/client/impl/nio/DefaultByteBufferFactory.java new file mode 100644 index 0000000000..f1bb528b04 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/DefaultByteBufferFactory.java @@ -0,0 +1,62 @@ +package com.rabbitmq.client.impl.nio; + +import java.nio.ByteBuffer; +import java.util.function.Function; + +/** + * Default {@link ByteBufferFactory} that creates heap-based {@link ByteBuffer}s. + * This behavior can be changed by passing in a custom {@link Function} + * to the constructor. + * + * @see NioParams + * @see ByteBufferFactory + * @since 5.5.0 + */ +public class DefaultByteBufferFactory implements ByteBufferFactory { + + private final Function allocator; + + public DefaultByteBufferFactory(Function allocator) { + this.allocator = allocator; + } + + public DefaultByteBufferFactory() { + this(capacity -> ByteBuffer.allocate(capacity)); + } + + @Override + public ByteBuffer createReadBuffer(NioContext nioContext) { + if (nioContext.getSslEngine() == null) { + return allocator.apply(nioContext.getNioParams().getReadByteBufferSize()); + } else { + return allocator.apply(nioContext.getSslEngine().getSession().getApplicationBufferSize()); + } + } + + @Override + public ByteBuffer createWriteBuffer(NioContext nioContext) { + if (nioContext.getSslEngine() == null) { + return allocator.apply(nioContext.getNioParams().getWriteByteBufferSize()); + } else { + return allocator.apply(nioContext.getSslEngine().getSession().getApplicationBufferSize()); + } + } + + @Override + public ByteBuffer createEncryptedReadBuffer(NioContext nioContext) { + return createEncryptedByteBuffer(nioContext); + } + + @Override + public ByteBuffer createEncryptedWriteBuffer(NioContext nioContext) { + return createEncryptedByteBuffer(nioContext); + } + + protected ByteBuffer createEncryptedByteBuffer(NioContext nioContext) { + if (nioContext.getSslEngine() == null) { + throw new IllegalArgumentException("Encrypted byte buffer should be created only in SSL/TLS context"); + } else { + return allocator.apply(nioContext.getSslEngine().getSession().getPacketBufferSize()); + } + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/FrameBuilder.java b/src/main/java/com/rabbitmq/client/impl/nio/FrameBuilder.java new file mode 100644 index 0000000000..f9631d1598 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/FrameBuilder.java @@ -0,0 +1,219 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.MalformedFrameException; +import com.rabbitmq.client.impl.Frame; + +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import static java.lang.String.format; + +/** + * Class to create AMQP frames from a {@link ReadableByteChannel}. + * Supports partial frames: a frame can be read in several attempts + * from the {@link NioLoop}. This can happen when the channel won't + * read any more bytes in the middle of a frame building. The state + * of the outstanding frame is saved up, and the builder will + * start where it left off when the {@link NioLoop} comes back to + * this connection. + * This class is not thread safe. + * + * @since 4.4.0 + */ +public class FrameBuilder { + + private static final int PAYLOAD_OFFSET = 1 /* type */ + 2 /* channel */ + 4 /* payload size */; + + protected final ReadableByteChannel channel; + + protected final ByteBuffer applicationBuffer; + private final int maxPayloadSize; + // to store the bytes of the outstanding data + // 3 byte-long because the longest we read is an unsigned int + // (not need to store the latest byte) + private final int[] frameBuffer = new int[3]; + private int frameType; + private int frameChannel; + private byte[] framePayload; + private int bytesRead = 0; + + public FrameBuilder(ReadableByteChannel channel, ByteBuffer buffer, int maxPayloadSize) { + this.channel = channel; + this.applicationBuffer = buffer; + this.maxPayloadSize = maxPayloadSize; + } + + /** + * Read a frame from the network. + * This method returns null if a frame could not have been fully built from + * the network. The client must then retry later (typically + * when the channel notifies it has something to read). + * + * @return a complete frame or null if a frame couldn't have been fully built + * @throws IOException + * @see Frame#readFrom(DataInputStream, int) + */ + public Frame readFrame() throws IOException { + while (somethingToRead()) { + if (bytesRead == 0) { + // type + frameType = readFromBuffer(); + if (frameType == 'A') { + handleProtocolVersionMismatch(); + } + } else if (bytesRead == 1) { + // channel 1/2 + frameBuffer[0] = readFromBuffer(); + } else if (bytesRead == 2) { + // channel 2/2 + frameChannel = (frameBuffer[0] << 8) + readFromBuffer(); + } else if (bytesRead == 3) { + // payload size 1/4 + frameBuffer[0] = readFromBuffer(); + } else if (bytesRead == 4) { + // payload size 2/4 + frameBuffer[1] = readFromBuffer(); + } else if (bytesRead == 5) { + // payload size 3/4 + frameBuffer[2] = readFromBuffer(); + } else if (bytesRead == 6) { + // payload size 4/4 + int framePayloadSize = (frameBuffer[0] << 24) + (frameBuffer[1] << 16) + (frameBuffer[2] << 8) + readFromBuffer(); + if (framePayloadSize >= maxPayloadSize) { + throw new IllegalStateException(format( + "Frame body is too large (%d), maximum configured size is %d. " + + "See ConnectionFactory#setMaxInboundMessageBodySize " + + "if you need to increase the limit.", + framePayloadSize, maxPayloadSize + )); + } + framePayload = new byte[framePayloadSize]; + } else if (bytesRead >= PAYLOAD_OFFSET && bytesRead < framePayload.length + PAYLOAD_OFFSET) { + framePayload[bytesRead - PAYLOAD_OFFSET] = (byte) readFromBuffer(); + } else if (bytesRead == framePayload.length + PAYLOAD_OFFSET) { + int frameEndMarker = readFromBuffer(); + if (frameEndMarker != AMQP.FRAME_END) { + throw new MalformedFrameException("Bad frame end marker: " + frameEndMarker); + } + bytesRead = 0; + return new Frame(frameType, frameChannel, framePayload); + } else { + throw new IllegalStateException("Number of read bytes incorrect: " + bytesRead); + } + bytesRead++; + } + return null; + } + + /** + * Tells whether there's something to read in the application buffer or not. + * Tries to read from the network if necessary. + * + * @return true if there's something to read in the application buffer + * @throws IOException + */ + protected boolean somethingToRead() throws IOException { + if (!applicationBuffer.hasRemaining()) { + applicationBuffer.clear(); + int read = NioHelper.read(channel, applicationBuffer); + applicationBuffer.flip(); + if (read > 0) { + return true; + } else { + return false; + } + } else { + return true; + } + } + + private int readFromBuffer() { + return applicationBuffer.get() & 0xff; + } + + /** + * Handle a protocol version mismatch. + * @return + * @throws IOException + * @see Frame#protocolVersionMismatch(DataInputStream) + */ + private void handleProtocolVersionMismatch() throws IOException { + // Probably an AMQP.... header indicating a version mismatch + // Otherwise meaningless, so try to read the version, + // and throw an exception, whether we read the version + // okay or not. + // Try to read everything from the network, this header + // is small and should never require several network reads. + byte[] expectedBytes = new byte[] { 'M', 'Q', 'P' }; + int expectedBytesCount = 0; + while (somethingToRead() && expectedBytesCount < 3) { + // We expect the letters M, Q, P in that order: generate an informative error if they're not found + int nextByte = readFromBuffer(); + if (nextByte != expectedBytes[expectedBytesCount]) { + throw new MalformedFrameException("Invalid AMQP protocol header from server: expected character " + + expectedBytes[expectedBytesCount] + ", got " + nextByte); + } + expectedBytesCount++; + } + + if (expectedBytesCount != 3) { + throw new MalformedFrameException("Invalid AMQP protocol header from server: read only " + + (expectedBytesCount + 1) + " byte(s) instead of 4"); + } + + int[] signature = new int[4]; + + for (int i = 0; i < 4; i++) { + if (somethingToRead()) { + signature[i] = readFromBuffer(); + } else { + throw new MalformedFrameException("Invalid AMQP protocol header from server"); + } + } + + MalformedFrameException x; + + if (signature[0] == 1 && + signature[1] == 1 && + signature[2] == 8 && + signature[3] == 0) { + x = new MalformedFrameException("AMQP protocol version mismatch; we are version " + + AMQP.PROTOCOL.MAJOR + "-" + AMQP.PROTOCOL.MINOR + "-" + AMQP.PROTOCOL.REVISION + + ", server is 0-8"); + } else { + String sig = ""; + for (int i = 0; i < 4; i++) { + if (i != 0) + sig += ","; + sig += signature[i]; + } + + x = new MalformedFrameException("AMQP protocol version mismatch; we are version " + + AMQP.PROTOCOL.MAJOR + "-" + AMQP.PROTOCOL.MINOR + "-" + AMQP.PROTOCOL.REVISION + + ", server sent signature " + sig); + } + throw x; + } + + //Indicates ssl underflow state - means that cipherBuffer should aggregate next chunks of bytes + public boolean isUnderflowHandlingEnabled() { + return false; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/FrameWriteRequest.java b/src/main/java/com/rabbitmq/client/impl/nio/FrameWriteRequest.java new file mode 100644 index 0000000000..961e3a4f19 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/FrameWriteRequest.java @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import com.rabbitmq.client.impl.Frame; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * + */ +public class FrameWriteRequest implements WriteRequest { + + final Frame frame; + + public FrameWriteRequest(Frame frame) { + this.frame = frame; + } + + @Override + public void handle(DataOutputStream outputStream) throws IOException { + frame.writeTo(outputStream); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/HeaderWriteRequest.java b/src/main/java/com/rabbitmq/client/impl/nio/HeaderWriteRequest.java new file mode 100644 index 0000000000..3559d91e86 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/HeaderWriteRequest.java @@ -0,0 +1,40 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import com.rabbitmq.client.AMQP; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * + */ +public class HeaderWriteRequest implements WriteRequest { + + public static final WriteRequest SINGLETON = new HeaderWriteRequest(); + + private HeaderWriteRequest() { } + + @Override + public void handle(DataOutputStream outputStream) throws IOException { + outputStream.write("AMQP".getBytes("US-ASCII")); + outputStream.write(0); + outputStream.write(AMQP.PROTOCOL.MAJOR); + outputStream.write(AMQP.PROTOCOL.MINOR); + outputStream.write(AMQP.PROTOCOL.REVISION); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/NioContext.java b/src/main/java/com/rabbitmq/client/impl/nio/NioContext.java new file mode 100644 index 0000000000..fe89375ae8 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/NioContext.java @@ -0,0 +1,40 @@ +package com.rabbitmq.client.impl.nio; + +import javax.net.ssl.SSLEngine; + +/** + * Context when creating resources for a NIO-based connection. + * + * @see ByteBufferFactory + * @since 5.5.0 + */ +public class NioContext { + + private final NioParams nioParams; + + private final SSLEngine sslEngine; + + NioContext(NioParams nioParams, SSLEngine sslEngine) { + this.nioParams = nioParams; + this.sslEngine = sslEngine; + } + + /** + * NIO params. + * + * @return + */ + public NioParams getNioParams() { + return nioParams; + } + + /** + * {@link SSLEngine} for SSL/TLS connection. + * Null for plain connection. + * + * @return + */ + public SSLEngine getSslEngine() { + return sslEngine; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/NioHelper.java b/src/main/java/com/rabbitmq/client/impl/nio/NioHelper.java new file mode 100644 index 0000000000..a0521b03c6 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/NioHelper.java @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; + +public class NioHelper { + + static int read(ReadableByteChannel channel, ByteBuffer buffer) throws IOException { + int read = channel.read(buffer); + if(read < 0) { + throw new IOException("I/O thread: reached EOF"); + } + return read; + } + +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/NioLoop.java b/src/main/java/com/rabbitmq/client/impl/nio/NioLoop.java new file mode 100644 index 0000000000..7894320768 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/NioLoop.java @@ -0,0 +1,325 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import com.rabbitmq.client.impl.Environment; +import com.rabbitmq.client.impl.Frame; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadFactory; + +/** + * Logic of the NIO loop. + */ +public class NioLoop implements Runnable { + + private static final Logger LOGGER = LoggerFactory.getLogger(NioLoop.class); + + private final NioLoopContext context; + + private final NioParams nioParams; + + private final ExecutorService connectionShutdownExecutor; + + public NioLoop(NioParams nioParams, NioLoopContext loopContext) { + this.nioParams = nioParams; + this.context = loopContext; + this.connectionShutdownExecutor = nioParams.getConnectionShutdownExecutor(); + } + + @Override + public void run() { + final SelectorHolder selectorState = context.readSelectorState; + final Selector selector = selectorState.selector; + final Set registrations = selectorState.registrations; + + final ByteBuffer buffer = context.readBuffer; + + final SelectorHolder writeSelectorState = context.writeSelectorState; + final Selector writeSelector = writeSelectorState.selector; + final Set writeRegistrations = writeSelectorState.registrations; + + // whether there have been write registrations in the previous loop + // registrations are done after Selector.select(), to work on clean keys + // thus, any write operation is performed in the next loop + // we don't want to wait in the read Selector.select() if there are + // pending writes + boolean writeRegistered = false; + + try { + while (!Thread.currentThread().isInterrupted()) { + + for (SelectionKey selectionKey : selector.keys()) { + SocketChannelFrameHandlerState state = (SocketChannelFrameHandlerState) selectionKey.attachment(); + if (state.getConnection() != null && state.getHeartbeatNanoSeconds() > 0) { + long now = System.nanoTime(); + if ((now - state.getLastActivity()) > state.getHeartbeatNanoSeconds() * 2) { + try { + handleHeartbeatFailure(state); + } catch (Exception e) { + LOGGER.warn("Error after heartbeat failure of connection {}", state.getConnection()); + } finally { + selectionKey.cancel(); + } + } + } + } + + int select; + if (!writeRegistered && registrations.isEmpty() && writeRegistrations.isEmpty()) { + // we can block, registrations will call Selector.wakeup() + select = selector.select(1000); + if (selector.keys().isEmpty()) { + // we haven't been doing anything for a while, shutdown state + boolean clean = context.cleanUp(); + if (clean) { + // we stop this thread + return; + } + // there may be incoming connections, keep going + } + } else { + // we don't have to block, we need to select and clean cancelled keys before registration + select = selector.selectNow(); + } + + writeRegistered = false; + + // registrations should be done after select, + // once the cancelled keys have been actually removed + SocketChannelRegistration registration; + Iterator registrationIterator = registrations.iterator(); + while (registrationIterator.hasNext()) { + registration = registrationIterator.next(); + registrationIterator.remove(); + int operations = registration.operations; + try { + if (registration.state.getChannel().isOpen()) { + registration.state.getChannel().register(selector, operations, registration.state); + } + } catch (Exception e) { + // can happen if the channel has been closed since the operation has been enqueued + LOGGER.info("Error while registering socket channel for read: {}", e.getMessage()); + } + } + + if (select > 0) { + Set readyKeys = selector.selectedKeys(); + Iterator iterator = readyKeys.iterator(); + while (iterator.hasNext()) { + SelectionKey key = iterator.next(); + iterator.remove(); + + if (!key.isValid()) { + continue; + } + final SocketChannelFrameHandlerState state = (SocketChannelFrameHandlerState) key.attachment(); + try { + if (key.isReadable()) { + if (!state.getChannel().isOpen()) { + key.cancel(); + continue; + } + if(state.getConnection() == null) { + // we're in AMQConnection#start, between the header sending and the FrameHandler#initialize + // let's wait a bit more + continue; + } + + state.prepareForReadSequence(); + + while (state.continueReading()) { + final Frame frame = state.frameBuilder.readFrame(); + + if (frame != null) { + try { + state.getConnection().ioLoopThread(Thread.currentThread()); + boolean noProblem = state.getConnection().handleReadFrame(frame); + if (noProblem && (!state.getConnection().isRunning() || state.getConnection().hasBrokerInitiatedShutdown())) { + // looks like the frame was Close-Ok or Close + dispatchShutdownToConnection(state); + key.cancel(); + break; + } + } catch (Throwable ex) { + // problem during frame processing, tell connection, and + // we can stop for this channel + handleIoError(state, ex); + key.cancel(); + break; + } + } + } + + state.setLastActivity(System.nanoTime()); + } + } catch (final Exception e) { + LOGGER.warn("Error during reading frames", e); + handleIoError(state, e); + key.cancel(); + } finally { + buffer.clear(); + } + } + } + + // write loop + + select = writeSelector.selectNow(); + + // registrations should be done after select, + // once the cancelled keys have been actually removed + SocketChannelRegistration writeRegistration; + Iterator writeRegistrationIterator = writeRegistrations.iterator(); + while (writeRegistrationIterator.hasNext()) { + writeRegistration = writeRegistrationIterator.next(); + writeRegistrationIterator.remove(); + int operations = writeRegistration.operations; + try { + if (writeRegistration.state.getChannel().isOpen()) { + writeRegistration.state.getChannel().register(writeSelector, operations, writeRegistration.state); + writeRegistered = true; + } + } catch (Exception e) { + // can happen if the channel has been closed since the operation has been enqueued + LOGGER.info("Error while registering socket channel for write: {}", e.getMessage()); + } + } + + if (select > 0) { + Set readyKeys = writeSelector.selectedKeys(); + Iterator iterator = readyKeys.iterator(); + while (iterator.hasNext()) { + SelectionKey key = iterator.next(); + iterator.remove(); + SocketChannelFrameHandlerState state = (SocketChannelFrameHandlerState) key.attachment(); + + if (!key.isValid()) { + continue; + } + + try { + if (key.isWritable()) { + if (!state.getChannel().isOpen()) { + key.cancel(); + continue; + } + + state.prepareForWriteSequence(); + + int toBeWritten = state.getWriteQueue().size(); + int written = 0; + + DataOutputStream outputStream = state.outputStream; + + WriteRequest request; + while (written <= toBeWritten && (request = state.getWriteQueue().poll()) != null) { + request.handle(outputStream); + written++; + } + outputStream.flush(); + } + } catch (Exception e) { + handleIoError(state, e); + } finally { + state.endWriteSequence(); + key.cancel(); + } + } + } + } + } catch (Exception e) { + LOGGER.error("Error in NIO loop", e); + } + } + + protected void handleIoError(SocketChannelFrameHandlerState state, Throwable ex) { + if (needToDispatchIoError(state)) { + dispatchIoErrorToConnection(state, ex); + } else { + try { + state.close(); + } catch (IOException ignored) { + + } + } + } + + protected void handleHeartbeatFailure(SocketChannelFrameHandlerState state) { + if (needToDispatchIoError(state)) { + dispatchShutdownToConnection( + () -> state.getConnection().handleHeartbeatFailure(), + state.getConnection().toString() + ); + } else { + try { + state.close(); + } catch (IOException ignored) { + + } + } + } + + protected boolean needToDispatchIoError(final SocketChannelFrameHandlerState state) { + return state.getConnection().isOpen(); + } + + protected void dispatchIoErrorToConnection(final SocketChannelFrameHandlerState state, final Throwable ex) { + dispatchShutdownToConnection( + () -> state.getConnection().handleIoError(ex), + state.getConnection().toString() + ); + } + + protected void dispatchShutdownToConnection(final SocketChannelFrameHandlerState state) { + dispatchShutdownToConnection( + () -> state.getConnection().doFinalShutdown(), + state.getConnection().toString() + ); + } + + protected void dispatchShutdownToConnection(Runnable connectionShutdownRunnable, String connectionName) { + // In case of recovery after the shutdown, + // the new connection shouldn't be initialized in + // the NIO thread, to avoid a deadlock. + if (this.connectionShutdownExecutor != null) { + connectionShutdownExecutor.execute(connectionShutdownRunnable); + } else if (executorService() != null) { + executorService().execute(connectionShutdownRunnable); + } else { + String name = "rabbitmq-connection-shutdown-" + connectionName; + Thread shutdownThread = Environment.newThread(threadFactory(), connectionShutdownRunnable, name); + shutdownThread.start(); + } + } + + private ExecutorService executorService() { + return nioParams.getNioExecutor(); + } + + private ThreadFactory threadFactory() { + return nioParams.getThreadFactory(); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/NioLoopContext.java b/src/main/java/com/rabbitmq/client/impl/nio/NioLoopContext.java new file mode 100644 index 0000000000..a31f142de0 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/NioLoopContext.java @@ -0,0 +1,116 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import com.rabbitmq.client.impl.Environment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Selector; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadFactory; + +/** + * + */ +public class NioLoopContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(NioLoopContext.class); + + private final SocketChannelFrameHandlerFactory socketChannelFrameHandlerFactory; + + private final ExecutorService executorService; + + private final ThreadFactory threadFactory; + + final ByteBuffer readBuffer, writeBuffer; + + SelectorHolder readSelectorState; + SelectorHolder writeSelectorState; + + public NioLoopContext(SocketChannelFrameHandlerFactory socketChannelFrameHandlerFactory, + NioParams nioParams) { + this.socketChannelFrameHandlerFactory = socketChannelFrameHandlerFactory; + this.executorService = nioParams.getNioExecutor(); + this.threadFactory = nioParams.getThreadFactory(); + NioContext nioContext = new NioContext(nioParams, null); + this.readBuffer = nioParams.getByteBufferFactory().createReadBuffer(nioContext); + this.writeBuffer = nioParams.getByteBufferFactory().createWriteBuffer(nioContext); + } + + void initStateIfNecessary() throws IOException { + // This code is supposed to be called only from the SocketChannelFrameHandlerFactory + // and while holding the lock. + // We lock just in case some other code calls this method in the future. + socketChannelFrameHandlerFactory.lock(); + try { + if (this.readSelectorState == null) { + this.readSelectorState = new SelectorHolder(Selector.open()); + this.writeSelectorState = new SelectorHolder(Selector.open()); + + startIoLoops(); + } + } finally { + socketChannelFrameHandlerFactory.unlock(); + } + } + + private void startIoLoops() { + if (executorService == null) { + Thread nioThread = Environment.newThread( + threadFactory, + new NioLoop(socketChannelFrameHandlerFactory.nioParams, this), + "rabbitmq-nio" + ); + nioThread.start(); + } else { + this.executorService.submit(new NioLoop(socketChannelFrameHandlerFactory.nioParams, this)); + } + } + + protected boolean cleanUp() { + int readRegistrationsCount = readSelectorState.registrations.size(); + if(readRegistrationsCount != 0) { + return false; + } + socketChannelFrameHandlerFactory.lock(); + try { + if (readRegistrationsCount != readSelectorState.registrations.size()) { + // a connection request has come in meanwhile, don't do anything + return false; + } + + try { + readSelectorState.selector.close(); + } catch (IOException e) { + LOGGER.warn("Could not close read selector: {}", e.getMessage()); + } + try { + writeSelectorState.selector.close(); + } catch (IOException e) { + LOGGER.warn("Could not close write selector: {}", e.getMessage()); + } + + this.readSelectorState = null; + this.writeSelectorState = null; + } finally { + socketChannelFrameHandlerFactory.unlock(); + } + return true; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/NioParams.java b/src/main/java/com/rabbitmq/client/impl/nio/NioParams.java new file mode 100644 index 0000000000..1d049189da --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/NioParams.java @@ -0,0 +1,428 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import com.rabbitmq.client.SocketChannelConfigurator; +import com.rabbitmq.client.SocketChannelConfigurators; +import com.rabbitmq.client.SslEngineConfigurator; + +import javax.net.ssl.SSLEngine; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.function.Function; + +import static com.rabbitmq.client.SslEngineConfigurators.ENABLE_HOSTNAME_VERIFICATION; + +/** + * Parameters used to configure the NIO mode of a {@link com.rabbitmq.client.ConnectionFactory}. + * + * @since 4.0.0 + */ +public class NioParams { + + static Function DEFAULT_WRITE_QUEUE_FACTORY = + ctx -> new BlockingQueueNioQueue( + new ArrayBlockingQueue<>(ctx.getNioParams().getWriteQueueCapacity(), true), + ctx.getNioParams().getWriteEnqueuingTimeoutInMs() + ); + + /** + * size of the byte buffer used for inbound data + */ + private int readByteBufferSize = 32768; + + /** + * size of the byte buffer used for outbound data + */ + private int writeByteBufferSize = 32768; + + /** + * the max number of IO threads + */ + private int nbIoThreads = 1; + + /** + * the timeout to enqueue outbound frames + */ + private int writeEnqueuingTimeoutInMs = 10 * 1000; + + /** + * the capacity of the queue used for outbound frames + */ + private int writeQueueCapacity = 10000; + + /** + * the executor service used for IO threads and connections shutdown + */ + private ExecutorService nioExecutor; + + /** + * the thread factory used for IO threads and connections shutdown + */ + private ThreadFactory threadFactory; + + /** + * the hook to configure the socket channel before it's open + */ + private SocketChannelConfigurator socketChannelConfigurator = SocketChannelConfigurators.defaultConfigurator(); + + /** + * the hook to configure the SSL engine before the connection is open + */ + private SslEngineConfigurator sslEngineConfigurator = sslEngine -> { + }; + + /** + * the executor service used for connection shutdown + * + * @since 5.4.0 + */ + private ExecutorService connectionShutdownExecutor; + + /** + * The factory to create {@link java.nio.ByteBuffer}s. + * The default is to create heap-based {@link java.nio.ByteBuffer}s. + * + * @since 5.5.0 + */ + private ByteBufferFactory byteBufferFactory = new DefaultByteBufferFactory(); + + /** + * Factory to create a {@link NioQueue}. + * + * @since 5.5.0 + */ + private Function writeQueueFactory = + DEFAULT_WRITE_QUEUE_FACTORY; + + public NioParams() { + } + + public NioParams(NioParams nioParams) { + setReadByteBufferSize(nioParams.getReadByteBufferSize()); + setWriteByteBufferSize(nioParams.getWriteByteBufferSize()); + setNbIoThreads(nioParams.getNbIoThreads()); + setWriteEnqueuingTimeoutInMs(nioParams.getWriteEnqueuingTimeoutInMs()); + setWriteQueueCapacity(nioParams.getWriteQueueCapacity()); + setNioExecutor(nioParams.getNioExecutor()); + setThreadFactory(nioParams.getThreadFactory()); + setSocketChannelConfigurator(nioParams.getSocketChannelConfigurator()); + setSslEngineConfigurator(nioParams.getSslEngineConfigurator()); + setConnectionShutdownExecutor(nioParams.getConnectionShutdownExecutor()); + setByteBufferFactory(nioParams.getByteBufferFactory()); + setWriteQueueFactory(nioParams.getWriteQueueFactory()); + } + + /** + * Enable server hostname verification for TLS connections. + * + * @return this {@link NioParams} instance + * @see NioParams#setSslEngineConfigurator(SslEngineConfigurator) + * @see com.rabbitmq.client.SslEngineConfigurators#ENABLE_HOSTNAME_VERIFICATION + */ + public NioParams enableHostnameVerification() { + if (this.sslEngineConfigurator == null) { + this.sslEngineConfigurator = ENABLE_HOSTNAME_VERIFICATION; + } else { + this.sslEngineConfigurator = this.sslEngineConfigurator.andThen(ENABLE_HOSTNAME_VERIFICATION); + } + return this; + } + + public int getReadByteBufferSize() { + return readByteBufferSize; + } + + /** + * Sets the size in byte of the read {@link java.nio.ByteBuffer} used in the NIO loop. + * Default is 32768. + *

+ * This parameter isn't used when using SSL/TLS, where {@link java.nio.ByteBuffer} + * size is set up according to the {@link javax.net.ssl.SSLSession} packet size. + * + * @param readByteBufferSize size of the {@link java.nio.ByteBuffer} for inbound data + * @return this {@link NioParams} instance + */ + public NioParams setReadByteBufferSize(int readByteBufferSize) { + if (readByteBufferSize <= 0) { + throw new IllegalArgumentException("Buffer size must be greater than 0"); + } + this.readByteBufferSize = readByteBufferSize; + return this; + } + + public int getWriteByteBufferSize() { + return writeByteBufferSize; + } + + /** + * Sets the size in byte of the write {@link java.nio.ByteBuffer} used in the NIO loop. + * Default is 32768. + *

+ * This parameter isn't used when using SSL/TLS, where {@link java.nio.ByteBuffer} + * size is set up according to the {@link javax.net.ssl.SSLSession} packet size. + * + * @param writeByteBufferSize size of the {@link java.nio.ByteBuffer} used for outbound data + * @return this {@link NioParams} instance + */ + public NioParams setWriteByteBufferSize(int writeByteBufferSize) { + if (writeByteBufferSize <= 0) { + throw new IllegalArgumentException("Buffer size must be greater than 0"); + } + this.writeByteBufferSize = writeByteBufferSize; + return this; + } + + public int getNbIoThreads() { + return nbIoThreads; + } + + /** + * Sets the max number of threads/tasks used for NIO. Default is 1. + * Set this number according to the number of simultaneous connections + * and their activity. + * Threads/tasks are created as necessary (e.g. with 10 threads, when + * 10 connections have been created). + * Once a connection is created, it's assigned to a thread/task and + * all its IO activity is handled by this thread/task. + *

+ * When idle for a few seconds (i.e. without any connection to perform IO for), + * a thread/task stops and is recreated if necessary. + * + * @param nbIoThreads + * @return this {@link NioParams} instance + */ + public NioParams setNbIoThreads(int nbIoThreads) { + if (nbIoThreads <= 0) { + throw new IllegalArgumentException("Number of threads must be greater than 0"); + } + this.nbIoThreads = nbIoThreads; + return this; + } + + public int getWriteEnqueuingTimeoutInMs() { + return writeEnqueuingTimeoutInMs; + } + + /** + * Sets the timeout for queuing outbound frames. Default is 10,000 ms. + * Every requests to the server is divided into frames + * that are then queued in a {@link java.util.concurrent.BlockingQueue} before + * being sent on the network by a IO thread. + *

+ * If the IO thread cannot cope with the frames dispatch, the + * {@link java.util.concurrent.BlockingQueue} gets filled up and blocks + * (blocking the calling thread by the same occasion). This timeout is the + * time the {@link java.util.concurrent.BlockingQueue} will wait before + * rejecting the outbound frame. The calling thread will then received + * an exception. + *

+ * The appropriate value depends on the application scenarios: + * rate of outbound data (published messages, acknowledgment, etc), network speed... + * + * @param writeEnqueuingTimeoutInMs + * @return this {@link NioParams} instance + * @see NioParams#setWriteQueueCapacity(int) + */ + public NioParams setWriteEnqueuingTimeoutInMs(int writeEnqueuingTimeoutInMs) { + this.writeEnqueuingTimeoutInMs = writeEnqueuingTimeoutInMs; + return this; + } + + public ExecutorService getNioExecutor() { + return nioExecutor; + } + + /** + * Sets the {@link ExecutorService} to use for NIO threads/tasks. + * Default is to use the thread factory. + *

+ * The {@link ExecutorService} should be able to run the + * number of requested IO threads, plus a few more, as it's also + * used to dispatch the shutdown of connections. + *

+ * Connection shutdown can also be handled by a dedicated {@link ExecutorService}, + * see {@link #setConnectionShutdownExecutor(ExecutorService)}. + *

+ * It's developer's responsibility to shut down the executor + * when it is no longer needed. + *

+ * The thread factory isn't used if an executor service is set up. + * + * @param nioExecutor {@link ExecutorService} used for IO threads and connection shutdown + * @return this {@link NioParams} instance + * @see NioParams#setNbIoThreads(int) + * @see NioParams#setThreadFactory(ThreadFactory) + * @see NioParams#setConnectionShutdownExecutor(ExecutorService) + */ + public NioParams setNioExecutor(ExecutorService nioExecutor) { + this.nioExecutor = nioExecutor; + return this; + } + + public ThreadFactory getThreadFactory() { + return threadFactory; + } + + /** + * Sets the {@link ThreadFactory} to use for NIO threads/tasks. + * Default is to use the {@link com.rabbitmq.client.ConnectionFactory}'s + * {@link ThreadFactory}. + *

+ * The {@link ThreadFactory} is used to spawn the IO threads + * and dispatch the shutdown of connections. + * + * @param threadFactory {@link ThreadFactory} used for IO threads and connection shutdown + * @return this {@link NioParams} instance + * @see NioParams#setNbIoThreads(int) + * @see NioParams#setNioExecutor(ExecutorService) + */ + public NioParams setThreadFactory(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + return this; + } + + public int getWriteQueueCapacity() { + return writeQueueCapacity; + } + + /** + * Set the capacity of the queue used for outbound frames. + * Default capacity is 10,000. + * + * @param writeQueueCapacity + * @return this {@link NioParams} instance + * @see NioParams#setWriteEnqueuingTimeoutInMs(int) + */ + public NioParams setWriteQueueCapacity(int writeQueueCapacity) { + if (writeQueueCapacity <= 0) { + throw new IllegalArgumentException("Write queue capacity must be greater than 0"); + } + this.writeQueueCapacity = writeQueueCapacity; + return this; + } + + public SocketChannelConfigurator getSocketChannelConfigurator() { + return socketChannelConfigurator; + } + + /** + * Set the {@link java.nio.channels.SocketChannel} configurator. + * This gets a chance to "configure" a socket channel + * before it has been opened. The default implementation disables + * Nagle's algorithm. + * + * @param configurator the configurator to use + * @return this {@link NioParams} instance + */ + public NioParams setSocketChannelConfigurator(SocketChannelConfigurator configurator) { + this.socketChannelConfigurator = configurator; + return this; + } + + public SslEngineConfigurator getSslEngineConfigurator() { + return sslEngineConfigurator; + } + + /** + * Set the {@link SSLEngine} configurator. + * This gets a change to "configure" the SSL engine + * before the connection has been opened. This can be + * used e.g. to set {@link javax.net.ssl.SSLParameters}. + * The default implementation doesn't do anything. + * + * @param configurator the configurator to use + * @return this {@link NioParams} instance + */ + public NioParams setSslEngineConfigurator(SslEngineConfigurator configurator) { + this.sslEngineConfigurator = configurator; + return this; + } + + public ExecutorService getConnectionShutdownExecutor() { + return connectionShutdownExecutor; + } + + /** + * Set the {@link ExecutorService} used for connection shutdown. + * If not set, falls back to the NIO executor and then the thread factory. + * This executor service is useful when strict control of the number of threads + * is necessary, the application can experience the closing of several connections + * at once, and automatic recovery is enabled. In such cases, the connection recovery + * can take place in the same pool of threads as the NIO operations, which can + * create deadlocks (all the threads of the pool are busy recovering, and there's no + * thread left for NIO, so connections never recover). + *

+ * Note it's developer's responsibility to shut down the executor + * when it is no longer needed. + *

+ * Using the thread factory for such scenarios avoid the deadlocks, at the price + * of potentially creating many short-lived threads in case of massive connection lost. + *

+ * With both the NIO and connection shutdown executor services set and configured + * accordingly, the application can control reliably the number of threads used. + * + * @param connectionShutdownExecutor the executor service to use + * @return this {@link NioParams} instance + * @see NioParams#setNioExecutor(ExecutorService) + * @since 5.4.0 + */ + public NioParams setConnectionShutdownExecutor(ExecutorService connectionShutdownExecutor) { + this.connectionShutdownExecutor = connectionShutdownExecutor; + return this; + } + + /** + * Set the factory to create {@link java.nio.ByteBuffer}s. + *

+ * The default implementation creates heap-based {@link java.nio.ByteBuffer}s. + * + * @param byteBufferFactory the factory to use + * @return this {@link NioParams} instance + * @see ByteBufferFactory + * @see DefaultByteBufferFactory + * @since 5.5.0 + */ + public NioParams setByteBufferFactory(ByteBufferFactory byteBufferFactory) { + this.byteBufferFactory = byteBufferFactory; + return this; + } + + public ByteBufferFactory getByteBufferFactory() { + return byteBufferFactory; + } + + /** + * Set the factory to create {@link NioQueue}s. + *

+ * The default uses a {@link ArrayBlockingQueue}. + * + * @param writeQueueFactory the factory to use + * @return this {@link NioParams} instance + * @see NioQueue + * @since 5.5.0 + */ + public NioParams setWriteQueueFactory( + Function writeQueueFactory) { + this.writeQueueFactory = writeQueueFactory; + return this; + } + + public Function getWriteQueueFactory() { + return writeQueueFactory; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/NioQueue.java b/src/main/java/com/rabbitmq/client/impl/nio/NioQueue.java new file mode 100644 index 0000000000..d15ab6ac6a --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/NioQueue.java @@ -0,0 +1,45 @@ +package com.rabbitmq.client.impl.nio; + +/** + * Contract to exchange frame between application threads and NIO thread. + *

+ * This is a simplified subset of {@link java.util.concurrent.BlockingQueue}. + * This interface is considered a SPI and is likely to move between + * minor and patch releases. + * + * @see NioParams + * @since 5.5.0 + */ +public interface NioQueue { + + /** + * Enqueue a frame, block if the queue is full. + * + * @param writeRequest + * @return + * @throws InterruptedException + */ + boolean offer(WriteRequest writeRequest) throws InterruptedException; + + /** + * Get the current size of the queue. + * + * @return + */ + int size(); + + /** + * Retrieves and removes the head of this queue, + * or returns {@code null} if this queue is empty. + * + * @return the head of this queue, or {@code null} if this queue is empty + */ + WriteRequest poll(); + + /** + * Returns {@code true} if the queue contains no element. + * + * @return {@code true} if the queue contains no element + */ + boolean isEmpty(); +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/SelectorHolder.java b/src/main/java/com/rabbitmq/client/impl/nio/SelectorHolder.java new file mode 100644 index 0000000000..48a058fcca --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/SelectorHolder.java @@ -0,0 +1,41 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import java.nio.channels.Selector; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + */ +public class SelectorHolder { + + final Selector selector; + + final Set registrations = Collections + .newSetFromMap(new ConcurrentHashMap()); + + SelectorHolder(Selector selector) { + this.selector = selector; + } + + public void registerFrameHandlerState(SocketChannelFrameHandlerState state, int operations) { + registrations.add(new SocketChannelRegistration(state, operations)); + selector.wakeup(); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelFrameHandler.java b/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelFrameHandler.java new file mode 100644 index 0000000000..656a9d7039 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelFrameHandler.java @@ -0,0 +1,112 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.Frame; +import com.rabbitmq.client.impl.FrameHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.SocketException; +import java.time.Duration; + +/** + * + */ +public class SocketChannelFrameHandler implements FrameHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(SocketChannelFrameHandler.class); + + private final SocketChannelFrameHandlerState state; + + public SocketChannelFrameHandler(SocketChannelFrameHandlerState state) { + this.state = state; + } + + @Override + public InetAddress getLocalAddress() { + return state.getChannel().socket().getLocalAddress(); + } + + @Override + public int getLocalPort() { + return state.getChannel().socket().getLocalPort(); + } + + @Override + public InetAddress getAddress() { + return state.getChannel().socket().getInetAddress(); + } + + @Override + public int getPort() { + return state.getChannel().socket().getPort(); + } + + @Override + public void setTimeout(int timeoutMs) throws SocketException { + state.getChannel().socket().setSoTimeout(timeoutMs); + if (state.getConnection() != null) { + state.setHeartbeat(Duration.ofSeconds(state.getConnection().getHeartbeat())); + } + } + + @Override + public int getTimeout() throws SocketException { + return state.getChannel().socket().getSoTimeout(); + } + + @Override + public void sendHeader() throws IOException { + state.sendHeader(); + } + + @Override + public void initialize(AMQConnection connection) { + state.setConnection(connection); + } + + @Override + public Frame readFrame() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeFrame(Frame frame) throws IOException { + state.write(frame); + } + + @Override + public void flush() throws IOException { + + } + + @Override + public void close() { + try { + state.close(); + } catch (IOException e) { + LOGGER.warn("Error while closing SocketChannel", e); + } + } + + public SocketChannelFrameHandlerState getState() { + return state; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelFrameHandlerFactory.java b/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelFrameHandlerFactory.java new file mode 100644 index 0000000000..34ec7d3f7d --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelFrameHandlerFactory.java @@ -0,0 +1,173 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import com.rabbitmq.client.Address; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.SslContextFactory; +import com.rabbitmq.client.impl.AbstractFrameHandlerFactory; +import com.rabbitmq.client.impl.FrameHandler; +import com.rabbitmq.client.impl.TlsUtils; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * + */ +public class SocketChannelFrameHandlerFactory extends AbstractFrameHandlerFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(SocketChannelFrameHandler.class); + + final NioParams nioParams; + + private final SslContextFactory sslContextFactory; + + private final Lock stateLock = new ReentrantLock(); + + private final AtomicLong globalConnectionCount = new AtomicLong(); + + private final List nioLoopContexts; + + public SocketChannelFrameHandlerFactory(int connectionTimeout, NioParams nioParams, boolean ssl, + SslContextFactory sslContextFactory, + int maxInboundMessageBodySize) { + super(connectionTimeout, null, ssl, maxInboundMessageBodySize); + this.nioParams = new NioParams(nioParams); + this.sslContextFactory = sslContextFactory; + this.nioLoopContexts = new ArrayList<>(this.nioParams.getNbIoThreads()); + for (int i = 0; i < this.nioParams.getNbIoThreads(); i++) { + this.nioLoopContexts.add(new NioLoopContext(this, this.nioParams)); + } + } + + @Override + public FrameHandler create(Address addr, String connectionName) throws IOException { + int portNumber = ConnectionFactory.portOrDefault(addr.getPort(), ssl); + + SSLEngine sslEngine = null; + SocketChannel channel = null; + + try { + if (ssl) { + SSLContext sslContext = sslContextFactory.create(connectionName); + sslEngine = sslContext.createSSLEngine(addr.getHost(), portNumber); + sslEngine.setUseClientMode(true); + if (nioParams.getSslEngineConfigurator() != null) { + nioParams.getSslEngineConfigurator().configure(sslEngine); + } + } + + SocketAddress address = addr.toInetSocketAddress(portNumber); + // No Sonar: the channel is closed in case of error and it cannot + // be closed here because it's part of the state of the connection + // to be returned. + channel = SocketChannel.open(); //NOSONAR + channel.configureBlocking(true); + if(nioParams.getSocketChannelConfigurator() != null) { + nioParams.getSocketChannelConfigurator().configure(channel); + } + + channel.socket().connect(address, this.connectionTimeout); + + + if (ssl) { + int initialSoTimeout = channel.socket().getSoTimeout(); + channel.socket().setSoTimeout(this.connectionTimeout); + sslEngine.beginHandshake(); + try { + ReadableByteChannel wrappedReadChannel = Channels.newChannel( + channel.socket().getInputStream()); + WritableByteChannel wrappedWriteChannel = Channels.newChannel( + channel.socket().getOutputStream()); + boolean handshake = SslEngineHelper.doHandshake( + wrappedWriteChannel, wrappedReadChannel, sslEngine); + if (!handshake) { + LOGGER.error("TLS connection failed"); + throw new SSLException("TLS handshake failed"); + } + channel.socket().setSoTimeout(initialSoTimeout); + } catch (SSLHandshakeException e) { + LOGGER.error("TLS connection failed: {}", e.getMessage()); + throw e; + } + TlsUtils.logPeerCertificateInfo(sslEngine.getSession()); + } + + channel.configureBlocking(false); + + // lock + stateLock.lock(); + NioLoopContext nioLoopContext = null; + try { + long modulo = globalConnectionCount.getAndIncrement() % nioParams.getNbIoThreads(); + nioLoopContext = nioLoopContexts.get((int) modulo); + nioLoopContext.initStateIfNecessary(); + SocketChannelFrameHandlerState state = new SocketChannelFrameHandlerState( + channel, + nioLoopContext, + nioParams, + sslEngine, + this.maxInboundMessageBodySize + ); + state.startReading(); + SocketChannelFrameHandler frameHandler = new SocketChannelFrameHandler(state); + return frameHandler; + } finally { + stateLock.unlock(); + } + + + } catch(IOException e) { + try { + if(sslEngine != null && channel != null) { + SslEngineHelper.close(channel, sslEngine); + } + if (channel != null) { + channel.close(); + } + } catch(IOException closingException) { + // ignore + } + throw e; + } + + } + + void lock() { + stateLock.lock(); + } + + void unlock() { + stateLock.unlock(); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelFrameHandlerState.java b/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelFrameHandlerState.java new file mode 100644 index 0000000000..89a5d6e45c --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelFrameHandlerState.java @@ -0,0 +1,249 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.Frame; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLEngine; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.time.Duration; + +/** + * + */ +public class SocketChannelFrameHandlerState { + + private static final Logger LOGGER = LoggerFactory.getLogger(SocketChannelFrameHandlerState.class); + + /** Time to linger before closing the socket forcefully. */ + private static final int SOCKET_CLOSING_TIMEOUT = 1; + + private final SocketChannel channel; + + private final NioQueue writeQueue; + + private volatile AMQConnection connection; + private volatile long heartbeatNanoSeconds = -1; + + /** should be used only in the NIO read thread */ + private long lastActivity; + + private final SelectorHolder writeSelectorState; + + private final SelectorHolder readSelectorState; + + final boolean ssl; + + final SSLEngine sslEngine; + + /** outbound app data (to be crypted if TLS is on) */ + final ByteBuffer plainOut; + + /** inbound app data (deciphered if TLS is on) */ + final ByteBuffer plainIn; + + /** outbound net data (ciphered if TLS is on) */ + final ByteBuffer cipherOut; + + /** inbound data (ciphered if TLS is on) */ + final ByteBuffer cipherIn; + + final DataOutputStream outputStream; + + final FrameBuilder frameBuilder; + + public SocketChannelFrameHandlerState(SocketChannel channel, NioLoopContext nioLoopsState, + NioParams nioParams, SSLEngine sslEngine, + int maxFramePayloadSize) { + this.channel = channel; + this.readSelectorState = nioLoopsState.readSelectorState; + this.writeSelectorState = nioLoopsState.writeSelectorState; + + NioContext nioContext = new NioContext(nioParams, sslEngine); + + this.writeQueue = nioParams.getWriteQueueFactory() == null ? + NioParams.DEFAULT_WRITE_QUEUE_FACTORY.apply(nioContext) : + nioParams.getWriteQueueFactory().apply(nioContext); + + this.sslEngine = sslEngine; + if(this.sslEngine == null) { + this.ssl = false; + this.plainOut = nioLoopsState.writeBuffer; + this.cipherOut = null; + this.plainIn = nioLoopsState.readBuffer; + this.cipherIn = null; + + this.outputStream = new DataOutputStream( + new ByteBufferOutputStream(channel, plainOut) + ); + + this.frameBuilder = new FrameBuilder(channel, plainIn, maxFramePayloadSize); + + } else { + this.ssl = true; + this.plainOut = nioParams.getByteBufferFactory().createWriteBuffer(nioContext); + this.cipherOut = nioParams.getByteBufferFactory().createEncryptedWriteBuffer(nioContext); + this.plainIn = nioParams.getByteBufferFactory().createReadBuffer(nioContext); + this.cipherIn = nioParams.getByteBufferFactory().createEncryptedReadBuffer(nioContext); + + this.outputStream = new DataOutputStream( + new SslEngineByteBufferOutputStream(sslEngine, plainOut, cipherOut, channel) + ); + this.frameBuilder = new SslEngineFrameBuilder(sslEngine, plainIn, + cipherIn, channel, maxFramePayloadSize); + } + + } + + public SocketChannel getChannel() { + return channel; + } + + public NioQueue getWriteQueue() { + return writeQueue; + } + + public void sendHeader() throws IOException { + sendWriteRequest(HeaderWriteRequest.SINGLETON); + } + + public void write(Frame frame) throws IOException { + sendWriteRequest(new FrameWriteRequest(frame)); + } + + private void sendWriteRequest(WriteRequest writeRequest) throws IOException { + try { + boolean offered = this.writeQueue.offer(writeRequest); + if(offered) { + this.writeSelectorState.registerFrameHandlerState(this, SelectionKey.OP_WRITE); + this.readSelectorState.selector.wakeup(); + } else { + throw new IOException("Frame enqueuing failed"); + } + } catch (InterruptedException e) { + LOGGER.warn("Thread interrupted during enqueuing frame in write queue"); + Thread.currentThread().interrupt(); + } + } + + public void startReading() { + this.readSelectorState.registerFrameHandlerState(this, SelectionKey.OP_READ); + } + + public AMQConnection getConnection() { + return connection; + } + + public void setConnection(AMQConnection connection) { + this.connection = connection; + } + + void setHeartbeat(Duration ht) { + this.heartbeatNanoSeconds = ht.toNanos(); + } + + public void setLastActivity(long lastActivity) { + this.lastActivity = lastActivity; + } + + public long getLastActivity() { + return lastActivity; + } + + long getHeartbeatNanoSeconds() { + return this.heartbeatNanoSeconds; + } + + void prepareForWriteSequence() { + if(ssl) { + plainOut.clear(); + cipherOut.clear(); + } + } + + void endWriteSequence() { + if(!ssl) { + plainOut.clear(); + } + } + + void prepareForReadSequence() throws IOException { + if(ssl) { + if (!frameBuilder.isUnderflowHandlingEnabled()) { + cipherIn.clear(); + cipherIn.flip(); + } + + plainIn.clear(); + plainIn.flip(); + + } else { + NioHelper.read(channel, plainIn); + plainIn.flip(); + } + } + + boolean continueReading() throws IOException { + if(ssl) { + if (frameBuilder.isUnderflowHandlingEnabled()) { + int bytesRead = NioHelper.read(channel, cipherIn); + if (bytesRead == 0) { + return false; + } else { + cipherIn.flip(); + return true; + } + } + if (!plainIn.hasRemaining() && !cipherIn.hasRemaining()) { + // need to try to read something + cipherIn.clear(); + int bytesRead = NioHelper.read(channel, cipherIn); + if (bytesRead == 0) { + return false; + } else { + cipherIn.flip(); + return true; + } + } else { + return true; + } + } else { + if (!plainIn.hasRemaining()) { + plainIn.clear(); + NioHelper.read(channel, plainIn); + plainIn.flip(); + } + return plainIn.hasRemaining(); + } + } + + void close() throws IOException { + if(ssl) { + SslEngineHelper.close(channel, sslEngine); + } + if(channel.isOpen()) { + channel.socket().setSoLinger(true, SOCKET_CLOSING_TIMEOUT); + channel.close(); + } + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelRegistration.java b/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelRegistration.java new file mode 100644 index 0000000000..256f534230 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/SocketChannelRegistration.java @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +/** + * + */ +public class SocketChannelRegistration { + + final SocketChannelFrameHandlerState state; + final int operations; + + public SocketChannelRegistration(SocketChannelFrameHandlerState state, int operations) { + this.state = state; + this.operations = operations; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + SocketChannelRegistration that = (SocketChannelRegistration) o; + + return state.getChannel().equals(that.state.getChannel()); + } + + @Override + public int hashCode() { + return state.getChannel().hashCode(); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/SslEngineByteBufferOutputStream.java b/src/main/java/com/rabbitmq/client/impl/nio/SslEngineByteBufferOutputStream.java new file mode 100644 index 0000000000..d8a0920a9f --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/SslEngineByteBufferOutputStream.java @@ -0,0 +1,62 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import javax.net.ssl.SSLEngine; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; + +/** + * Bridge between the byte buffer and stream worlds. + */ +public class SslEngineByteBufferOutputStream extends OutputStream { + + private final SSLEngine sslEngine; + + private final ByteBuffer plainOut, cypherOut; + + private final WritableByteChannel channel; + + public SslEngineByteBufferOutputStream(SSLEngine sslEngine, ByteBuffer plainOut, ByteBuffer cypherOut, WritableByteChannel channel) { + this.sslEngine = sslEngine; + this.plainOut = plainOut; + this.cypherOut = cypherOut; + this.channel = channel; + } + + @Override + public void write(int b) throws IOException { + if (!plainOut.hasRemaining()) { + doFlush(); + } + plainOut.put((byte) b); + } + + @Override + public void flush() throws IOException { + if (plainOut.position() > 0) { + doFlush(); + } + } + + private void doFlush() throws IOException { + plainOut.flip(); + SslEngineHelper.write(channel, sslEngine, plainOut, cypherOut); + plainOut.clear(); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/SslEngineFrameBuilder.java b/src/main/java/com/rabbitmq/client/impl/nio/SslEngineFrameBuilder.java new file mode 100644 index 0000000000..31601afa6c --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/SslEngineFrameBuilder.java @@ -0,0 +1,88 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; + + +/** + * Sub-class of {@link FrameBuilder} that unwraps crypted data from the network. + * @since 4.4.0 + */ +public class SslEngineFrameBuilder extends FrameBuilder { + + private final SSLEngine sslEngine; + + private final ByteBuffer cipherBuffer; + + private boolean isUnderflowHandlingEnabled = false; + + public SslEngineFrameBuilder(SSLEngine sslEngine, ByteBuffer plainIn, + ByteBuffer cipherIn, ReadableByteChannel channel, + int maxPayloadSize) { + super(channel, plainIn, maxPayloadSize); + this.sslEngine = sslEngine; + this.cipherBuffer = cipherIn; + } + + @Override + protected boolean somethingToRead() throws IOException { + if (applicationBuffer.hasRemaining() && !isUnderflowHandlingEnabled) { + return true; + } else { + applicationBuffer.clear(); + + boolean underflowHandling = false; + + try { + SSLEngineResult result = sslEngine.unwrap(cipherBuffer, applicationBuffer); + switch (result.getStatus()) { + case OK: + applicationBuffer.flip(); + if (applicationBuffer.hasRemaining()) { + return true; + } + applicationBuffer.clear(); + break; + case BUFFER_OVERFLOW: + throw new SSLException("buffer overflow in read"); + case BUFFER_UNDERFLOW: + cipherBuffer.compact(); + underflowHandling = true; + return false; + case CLOSED: + throw new SSLException("closed in read"); + default: + throw new IllegalStateException("Invalid SSL status: " + result.getStatus()); + } + } finally { + isUnderflowHandlingEnabled = underflowHandling; + } + + return false; + } + } + + @Override + public boolean isUnderflowHandlingEnabled() { + return isUnderflowHandlingEnabled; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/SslEngineHelper.java b/src/main/java/com/rabbitmq/client/impl/nio/SslEngineHelper.java new file mode 100644 index 0000000000..c191233f42 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/SslEngineHelper.java @@ -0,0 +1,243 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; +import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_TASK; +import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_WRAP; +import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + +/** + * + */ +public class SslEngineHelper { + + private static final Logger LOGGER = LoggerFactory.getLogger(SslEngineHelper.class); + + public static boolean doHandshake(WritableByteChannel writeChannel, ReadableByteChannel readChannel, SSLEngine engine) throws IOException { + + ByteBuffer plainOut = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize()); + ByteBuffer plainIn = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize()); + ByteBuffer cipherOut = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()); + ByteBuffer cipherIn = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()); + + LOGGER.debug("Starting TLS handshake"); + + SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); + LOGGER.debug("Initial handshake status is {}", handshakeStatus); + while (handshakeStatus != FINISHED && handshakeStatus != NOT_HANDSHAKING) { + LOGGER.debug("Handshake status is {}", handshakeStatus); + switch (handshakeStatus) { + case NEED_TASK: + LOGGER.debug("Running tasks"); + handshakeStatus = runDelegatedTasks(engine); + break; + case NEED_UNWRAP: + LOGGER.debug("Unwrapping..."); + handshakeStatus = unwrap(cipherIn, plainIn, readChannel, engine); + break; + case NEED_WRAP: + LOGGER.debug("Wrapping..."); + handshakeStatus = wrap(plainOut, cipherOut, writeChannel, engine); + break; + case FINISHED: + break; + case NOT_HANDSHAKING: + break; + default: + throw new SSLException("Unexpected handshake status " + handshakeStatus); + } + } + + + LOGGER.debug("TLS handshake completed"); + return true; + } + + private static SSLEngineResult.HandshakeStatus runDelegatedTasks(SSLEngine sslEngine) { + // FIXME run in executor? + Runnable runnable; + while ((runnable = sslEngine.getDelegatedTask()) != null) { + LOGGER.debug("Running delegated task"); + runnable.run(); + } + return sslEngine.getHandshakeStatus(); + } + + private static SSLEngineResult.HandshakeStatus unwrap(ByteBuffer cipherIn, ByteBuffer plainIn, + ReadableByteChannel channel, SSLEngine sslEngine) throws IOException { + SSLEngineResult.HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus(); + LOGGER.debug("Handshake status is {} before unwrapping", handshakeStatus); + + LOGGER.debug("Cipher in position {}", cipherIn.position()); + int read; + if (cipherIn.position() == 0) { + LOGGER.debug("Reading from channel"); + read = channel.read(cipherIn); + LOGGER.debug("Read {} byte(s) from channel", read); + if (read < 0) { + throw new SSLException("Could not read from socket channel"); + } + cipherIn.flip(); + } else { + LOGGER.debug("Not reading"); + } + + SSLEngineResult.Status status; + SSLEngineResult unwrapResult; + do { + int positionBeforeUnwrapping = cipherIn.position(); + LOGGER.debug("Before unwrapping cipherIn is {}, with {} remaining byte(s)", cipherIn, cipherIn.remaining()); + unwrapResult = sslEngine.unwrap(cipherIn, plainIn); + LOGGER.debug("SSL engine result is {} after unwrapping", unwrapResult); + status = unwrapResult.getStatus(); + switch (status) { + case OK: + plainIn.clear(); + if (unwrapResult.getHandshakeStatus() == NEED_TASK) { + handshakeStatus = runDelegatedTasks(sslEngine); + cipherIn.position(positionBeforeUnwrapping + unwrapResult.bytesConsumed()); + } else { + handshakeStatus = unwrapResult.getHandshakeStatus(); + } + break; + case BUFFER_OVERFLOW: + throw new SSLException("Buffer overflow during handshake"); + case BUFFER_UNDERFLOW: + LOGGER.debug("Buffer underflow"); + cipherIn.compact(); + LOGGER.debug("Reading from channel..."); + read = NioHelper.read(channel, cipherIn); + if(read <= 0) { + retryRead(channel, cipherIn); + } + LOGGER.debug("Done reading from channel..."); + cipherIn.flip(); + break; + case CLOSED: + sslEngine.closeInbound(); + break; + default: + throw new SSLException("Unexpected status from " + unwrapResult); + } + } + while (unwrapResult.getHandshakeStatus() != NEED_WRAP && unwrapResult.getHandshakeStatus() != FINISHED); + + LOGGER.debug("cipherIn position after unwrap {}", cipherIn.position()); + return handshakeStatus; + } + + private static int retryRead(ReadableByteChannel channel, ByteBuffer buffer) throws IOException { + int attempt = 0; + int read = 0; + while(attempt < 3) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + read = NioHelper.read(channel, buffer); + if(read > 0) { + break; + } + attempt++; + } + return read; + } + + private static SSLEngineResult.HandshakeStatus wrap(ByteBuffer plainOut, ByteBuffer cipherOut, + WritableByteChannel channel, SSLEngine sslEngine) throws IOException { + SSLEngineResult.HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus(); + LOGGER.debug("Handshake status is {} before wrapping", handshakeStatus); + SSLEngineResult result = sslEngine.wrap(plainOut, cipherOut); + LOGGER.debug("SSL engine result is {} after wrapping", result); + switch (result.getStatus()) { + case OK: + cipherOut.flip(); + while (cipherOut.hasRemaining()) { + int written = channel.write(cipherOut); + LOGGER.debug("Wrote {} byte(s)", written); + } + cipherOut.clear(); + if (result.getHandshakeStatus() == NEED_TASK) { + handshakeStatus = runDelegatedTasks(sslEngine); + } else { + handshakeStatus = result.getHandshakeStatus(); + } + + break; + case BUFFER_OVERFLOW: + throw new SSLException("Buffer overflow during handshake"); + default: + throw new SSLException("Unexpected status " + result.getStatus()); + } + return handshakeStatus; + } + + public static void write(WritableByteChannel socketChannel, SSLEngine engine, ByteBuffer plainOut, ByteBuffer cypherOut) throws IOException { + while (plainOut.hasRemaining()) { + cypherOut.clear(); + SSLEngineResult result = engine.wrap(plainOut, cypherOut); + switch (result.getStatus()) { + case OK: + cypherOut.flip(); + while (cypherOut.hasRemaining()) { + socketChannel.write(cypherOut); + } + break; + case BUFFER_OVERFLOW: + throw new SSLException("Buffer overflow occured after a wrap."); + case BUFFER_UNDERFLOW: + throw new SSLException("Buffer underflow occured after a wrap."); + case CLOSED: + throw new SSLException("Buffer closed"); + default: + throw new IllegalStateException("Invalid SSL status: " + result.getStatus()); + } + } + } + + public static void close(WritableByteChannel channel, SSLEngine engine) throws IOException { + ByteBuffer plainOut = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize()); + ByteBuffer cipherOut = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()); + + // won't be sending any more data + engine.closeOutbound(); + + while (!engine.isOutboundDone()) { + engine.wrap(plainOut, cipherOut); + cipherOut.flip(); + while (cipherOut.hasRemaining()) { + int num = channel.write(cipherOut); + if (num == -1) { + // the channel has been closed + break; + } + } + cipherOut.clear(); + } + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/WriteRequest.java b/src/main/java/com/rabbitmq/client/impl/nio/WriteRequest.java new file mode 100644 index 0000000000..1550af8246 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/WriteRequest.java @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.nio; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * + */ +public interface WriteRequest { + + void handle(DataOutputStream dataOutputStream) throws IOException; + +} diff --git a/src/main/java/com/rabbitmq/client/impl/nio/package-info.java b/src/main/java/com/rabbitmq/client/impl/nio/package-info.java new file mode 100644 index 0000000000..9d6f23e3cb --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/nio/package-info.java @@ -0,0 +1,4 @@ +/** + * NIO network connector. + */ +package com.rabbitmq.client.impl.nio; \ No newline at end of file diff --git a/src/main/java/com/rabbitmq/client/impl/package-info.java b/src/main/java/com/rabbitmq/client/impl/package-info.java new file mode 100644 index 0000000000..4b22e82833 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/package-info.java @@ -0,0 +1,4 @@ +/** + * Implementations of interfaces specified in the client API, and their supporting classes. + */ +package com.rabbitmq.client.impl; \ No newline at end of file diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringChannel.java b/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringChannel.java new file mode 100644 index 0000000000..b6be383c28 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringChannel.java @@ -0,0 +1,1017 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.AMQCommand; +import com.rabbitmq.client.impl.recovery.Utils.IoTimeoutExceptionRunnable; +import com.rabbitmq.utility.Utility; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeoutException; + +/** + * {@link com.rabbitmq.client.Channel} implementation that is automatically + * recovered during connection recovery. + * + * @since 3.3.0 + */ +public class AutorecoveringChannel implements RecoverableChannel { + + private static final Logger LOGGER = LoggerFactory.getLogger(AutorecoveringChannel.class); + + private volatile RecoveryAwareChannelN delegate; + private volatile AutorecoveringConnection connection; + private final List shutdownHooks = new CopyOnWriteArrayList<>(); + private final List recoveryListeners = new CopyOnWriteArrayList<>(); + private final List returnListeners = new CopyOnWriteArrayList<>(); + private final List confirmListeners = new CopyOnWriteArrayList<>(); + private final Set consumerTags = Collections.synchronizedSet(new HashSet<>()); + private int prefetchCountConsumer; + private int prefetchCountGlobal; + private boolean usesPublisherConfirms; + private boolean usesTransactions; + + public AutorecoveringChannel(AutorecoveringConnection connection, RecoveryAwareChannelN delegate) { + this.connection = connection; + this.delegate = delegate; + } + + @Override + public int getChannelNumber() { + return delegate.getChannelNumber(); + } + + @Override + public Connection getConnection() { + return delegate.getConnection(); + } + + public Channel getDelegate() { + return delegate; + } + + @Override + public void close() throws IOException, TimeoutException { + executeAndClean(() -> delegate.close()); + } + + @Override + public void close(int closeCode, String closeMessage) throws IOException, TimeoutException { + executeAndClean(() -> delegate.close(closeCode, closeMessage)); + } + + @Override + public void abort() { + this.delegate.abort(); + this.clean(); + } + + @Override + public void abort(int closeCode, String closeMessage) { + this.delegate.abort(closeCode, closeMessage != null ? closeMessage : ""); + this.clean(); + } + + /** + * Cleans up the channel in the following way: + *

+ * Removes every recorded consumer of the channel and finally unregisters the channel from + * the underlying connection to not process any further traffic. + */ + private void clean() { + for (String consumerTag : Utility.copy(consumerTags)) { + this.deleteRecordedConsumer(consumerTag); + } + this.connection.unregisterChannel(this); + } + + private void executeAndClean(IoTimeoutExceptionRunnable callback) throws IOException, TimeoutException { + try { + callback.run(); + } finally { + this.clean(); + } + } + + @Override + public void addReturnListener(ReturnListener listener) { + this.returnListeners.add(listener); + delegate.addReturnListener(listener); + } + + @Override + public ReturnListener addReturnListener(ReturnCallback returnCallback) { + ReturnListener returnListener = (replyCode, replyText, exchange, routingKey, properties, body) -> returnCallback.handle(new Return( + replyCode, replyText, exchange, routingKey, properties, body + )); + this.addReturnListener(returnListener); + return returnListener; + } + + @Override + public boolean removeReturnListener(ReturnListener listener) { + this.returnListeners.remove(listener); + return delegate.removeReturnListener(listener); + } + + @Override + public void clearReturnListeners() { + this.returnListeners.clear(); + delegate.clearReturnListeners(); + } + + @Override + public void addConfirmListener(ConfirmListener listener) { + this.confirmListeners.add(listener); + delegate.addConfirmListener(listener); + } + + @Override + public ConfirmListener addConfirmListener(ConfirmCallback ackCallback, ConfirmCallback nackCallback) { + ConfirmListener confirmListener = new ConfirmListener() { + + @Override + public void handleAck(long deliveryTag, boolean multiple) throws IOException { + ackCallback.handle(deliveryTag, multiple); + } + + @Override + public void handleNack(long deliveryTag, boolean multiple) throws IOException { + nackCallback.handle(deliveryTag, multiple); + } + }; + this.addConfirmListener(confirmListener); + return confirmListener; + } + + @Override + public boolean removeConfirmListener(ConfirmListener listener) { + this.confirmListeners.remove(listener); + return delegate.removeConfirmListener(listener); + } + + @Override + public void clearConfirmListeners() { + this.confirmListeners.clear(); + delegate.clearConfirmListeners(); + } + + @Override + public Consumer getDefaultConsumer() { + return delegate.getDefaultConsumer(); + } + + @Override + public void setDefaultConsumer(Consumer consumer) { + delegate.setDefaultConsumer(consumer); + } + + @Override + public void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException { + if (global) { + this.prefetchCountGlobal = prefetchCount; + } else { + this.prefetchCountConsumer = prefetchCount; + } + + delegate.basicQos(prefetchSize, prefetchCount, global); + } + + @Override + public void basicQos(int prefetchCount) throws IOException { + basicQos(0, prefetchCount, false); + } + + @Override + public void basicQos(int prefetchCount, boolean global) throws IOException { + basicQos(0, prefetchCount, global); + } + + @Override + public void basicPublish(String exchange, String routingKey, AMQP.BasicProperties props, byte[] body) throws IOException { + delegate.basicPublish(exchange, routingKey, props, body); + } + + @Override + public void basicPublish(String exchange, String routingKey, boolean mandatory, AMQP.BasicProperties props, byte[] body) throws IOException { + delegate.basicPublish(exchange, routingKey, mandatory, props, body); + } + + @Override + public void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, AMQP.BasicProperties props, byte[] body) throws IOException { + delegate.basicPublish(exchange, routingKey, mandatory, immediate, props, body); + } + + @Override + public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type) throws IOException { + return exchangeDeclare(exchange, type, false, false, null); + } + + @Override + public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type) throws IOException { + return exchangeDeclare(exchange, type.getType()); + } + + @Override + public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable) throws IOException { + return exchangeDeclare(exchange, type, durable, false, null); + } + + @Override + public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable) throws IOException { + return exchangeDeclare(exchange, type.getType(), durable); + } + + @Override + public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map arguments) throws IOException { + return exchangeDeclare(exchange, type, durable, autoDelete, false, arguments); + } + + @Override + public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, Map arguments) throws IOException { + return exchangeDeclare(exchange, type.getType(), durable, autoDelete, arguments); + } + + @Override + public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map arguments) throws IOException { + final AMQP.Exchange.DeclareOk ok = delegate.exchangeDeclare(exchange, type, durable, autoDelete, internal, arguments); + recordExchange(ok, exchange, type, durable, autoDelete, arguments); + return ok; + } + + @Override + public AMQP.Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map arguments) throws IOException { + return exchangeDeclare(exchange, type.getType(), durable, autoDelete, internal, arguments); + } + + @Override + public void exchangeDeclareNoWait(String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map arguments) throws IOException { + RecordedExchange x = new RecordedExchange(this, exchange). + type(type). + durable(durable). + autoDelete(autoDelete). + arguments(arguments); + recordExchange(exchange, x); + delegate.exchangeDeclareNoWait(exchange, type, durable, autoDelete, internal, arguments); + } + + @Override + public void exchangeDeclareNoWait(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map arguments) throws IOException { + exchangeDeclareNoWait(exchange, type.getType(), durable, autoDelete, internal, arguments); + } + + @Override + public AMQP.Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException { + return delegate.exchangeDeclarePassive(name); + } + + @Override + public AMQP.Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) throws IOException { + deleteRecordedExchange(exchange); + return delegate.exchangeDelete(exchange, ifUnused); + } + + @Override + public void exchangeDeleteNoWait(String exchange, boolean ifUnused) throws IOException { + deleteRecordedExchange(exchange); + delegate.exchangeDeleteNoWait(exchange, ifUnused); + } + + @Override + public AMQP.Exchange.DeleteOk exchangeDelete(String exchange) throws IOException { + return exchangeDelete(exchange, false); + } + + @Override + public AMQP.Exchange.BindOk exchangeBind(String destination, String source, String routingKey) throws IOException { + return exchangeBind(destination, source, routingKey, null); + } + + @Override + public AMQP.Exchange.BindOk exchangeBind(String destination, String source, String routingKey, Map arguments) throws IOException { + final AMQP.Exchange.BindOk ok = delegate.exchangeBind(destination, source, routingKey, arguments); + recordExchangeBinding(destination, source, routingKey, arguments); + return ok; + } + + @Override + public void exchangeBindNoWait(String destination, String source, String routingKey, Map arguments) throws IOException { + delegate.exchangeBindNoWait(destination, source, routingKey, arguments); + recordExchangeBinding(destination, source, routingKey, arguments); + } + + @Override + public AMQP.Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey) throws IOException { + return exchangeUnbind(destination, source, routingKey, null); + } + + @Override + public AMQP.Exchange.UnbindOk exchangeUnbind(String destination, String source, String routingKey, Map arguments) throws IOException { + deleteRecordedExchangeBinding(destination, source, routingKey, arguments); + this.maybeDeleteRecordedAutoDeleteExchange(source); + return delegate.exchangeUnbind(destination, source, routingKey, arguments); + } + + @Override + public void exchangeUnbindNoWait(String destination, String source, String routingKey, Map arguments) throws IOException { + delegate.exchangeUnbindNoWait(destination, source, routingKey, arguments); + deleteRecordedExchangeBinding(destination, source, routingKey, arguments); + } + + @Override + public AMQP.Queue.DeclareOk queueDeclare() throws IOException { + return queueDeclare("", false, true, true, null); + } + + @Override + public AMQP.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments) throws IOException { + final AMQP.Queue.DeclareOk ok = delegate.queueDeclare(queue, durable, exclusive, autoDelete, arguments); + recordQueue(ok, queue, durable, exclusive, autoDelete, arguments); + return ok; + } + + @Override + public void queueDeclareNoWait(String queue, + boolean durable, + boolean exclusive, + boolean autoDelete, + Map arguments) throws IOException { + RecordedQueue meta = new RecordedQueue(this, queue). + durable(durable). + exclusive(exclusive). + autoDelete(autoDelete). + arguments(arguments). + recoveredQueueNameSupplier(connection.getRecoveredQueueNameSupplier()); + delegate.queueDeclareNoWait(queue, durable, exclusive, autoDelete, arguments); + recordQueue(queue, meta); + + } + + @Override + public AMQP.Queue.DeclareOk queueDeclarePassive(String queue) throws IOException { + return delegate.queueDeclarePassive(queue); + } + + @Override + public long messageCount(String queue) throws IOException { + return delegate.messageCount(queue); + } + + @Override + public long consumerCount(String queue) throws IOException { + return delegate.consumerCount(queue); + } + + @Override + public AMQP.Queue.DeleteOk queueDelete(String queue) throws IOException { + return queueDelete(queue, false, false); + } + + @Override + public AMQP.Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty) throws IOException { + deleteRecordedQueue(queue); + return delegate.queueDelete(queue, ifUnused, ifEmpty); + } + + @Override + public void queueDeleteNoWait(String queue, boolean ifUnused, boolean ifEmpty) throws IOException { + deleteRecordedQueue(queue); + delegate.queueDeleteNoWait(queue, ifUnused, ifEmpty); + } + + @Override + public AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOException { + return queueBind(queue, exchange, routingKey, null); + } + + @Override + public AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map arguments) throws IOException { + AMQP.Queue.BindOk ok = delegate.queueBind(queue, exchange, routingKey, arguments); + recordQueueBinding(queue, exchange, routingKey, arguments); + return ok; + } + + @Override + public void queueBindNoWait(String queue, String exchange, String routingKey, Map arguments) throws IOException { + delegate.queueBindNoWait(queue, exchange, routingKey, arguments); + recordQueueBinding(queue, exchange, routingKey, arguments); + } + + @Override + public AMQP.Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey) throws IOException { + return queueUnbind(queue, exchange, routingKey, null); + } + + @Override + public AMQP.Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey, Map arguments) throws IOException { + deleteRecordedQueueBinding(queue, exchange, routingKey, arguments); + this.maybeDeleteRecordedAutoDeleteExchange(exchange); + return delegate.queueUnbind(queue, exchange, routingKey, arguments); + } + + @Override + public AMQP.Queue.PurgeOk queuePurge(String queue) throws IOException { + return delegate.queuePurge(queue); + } + + @Override + public GetResponse basicGet(String queue, boolean autoAck) throws IOException { + return delegate.basicGet(queue, autoAck); + } + + @Override + public void basicAck(long deliveryTag, boolean multiple) throws IOException { + delegate.basicAck(deliveryTag, multiple); + } + + @Override + public void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException { + delegate.basicNack(deliveryTag, multiple, requeue); + } + + @Override + public void basicReject(long deliveryTag, boolean requeue) throws IOException { + delegate.basicReject(deliveryTag, requeue); + } + + @Override + public String basicConsume(String queue, Consumer callback) throws IOException { + return basicConsume(queue, false, callback); + } + + @Override + public String basicConsume(String queue, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException { + return basicConsume(queue, consumerFromDeliverCancelCallbacks(deliverCallback, cancelCallback)); + } + + @Override + public String basicConsume(String queue, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, consumerFromDeliverShutdownCallbacks(deliverCallback, shutdownSignalCallback)); + } + + @Override + public String basicConsume(String queue, DeliverCallback deliverCallback, CancelCallback cancelCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, false, consumerFromDeliverCancelShutdownCallbacks(deliverCallback, cancelCallback, shutdownSignalCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException { + return basicConsume(queue, autoAck, "", callback); + } + + @Override + public String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException { + return basicConsume(queue, autoAck, "", consumerFromDeliverCancelCallbacks(deliverCallback, cancelCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) + throws IOException { + return basicConsume(queue, autoAck, "", consumerFromDeliverShutdownCallbacks(deliverCallback, shutdownSignalCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, DeliverCallback deliverCallback, CancelCallback cancelCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, "", consumerFromDeliverCancelShutdownCallbacks(deliverCallback, cancelCallback, shutdownSignalCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, Consumer callback) throws IOException { + return basicConsume(queue, autoAck, consumerTag, false, false, null, callback); + } + + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, CancelCallback cancelCallback) + throws IOException { + return basicConsume(queue, autoAck, consumerTag, false, false, null, consumerFromDeliverCancelCallbacks(deliverCallback, cancelCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, consumerTag, false, false, null, consumerFromDeliverShutdownCallbacks(deliverCallback, shutdownSignalCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, DeliverCallback deliverCallback, CancelCallback cancelCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, consumerTag, false, false, null, consumerFromDeliverCancelShutdownCallbacks(deliverCallback, cancelCallback, shutdownSignalCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, Map arguments, Consumer callback) throws IOException { + return basicConsume(queue, autoAck, "", false, false, arguments, callback); + } + + @Override + public String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback) + throws IOException { + return basicConsume(queue, autoAck, "", false, false, arguments, consumerFromDeliverCancelCallbacks(deliverCallback, cancelCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, "", false, false, arguments, consumerFromDeliverShutdownCallbacks(deliverCallback, shutdownSignalCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, "", false, false, arguments, consumerFromDeliverCancelShutdownCallbacks(deliverCallback, cancelCallback, shutdownSignalCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, Consumer callback) throws IOException { + final String tag = delegate.basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, callback); + recordConsumer(tag, queue, autoAck, exclusive, arguments, callback); + return tag; + } + + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, + DeliverCallback deliverCallback, CancelCallback cancelCallback) throws IOException { + return basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, consumerFromDeliverCancelCallbacks(deliverCallback, cancelCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, + DeliverCallback deliverCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, consumerFromDeliverShutdownCallbacks(deliverCallback, shutdownSignalCallback)); + } + + @Override + public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, + DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + return basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, consumerFromDeliverCancelShutdownCallbacks(deliverCallback, cancelCallback, shutdownSignalCallback)); + } + + private Consumer consumerFromDeliverCancelCallbacks(final DeliverCallback deliverCallback, final CancelCallback cancelCallback) { + return new Consumer() { + + @Override + public void handleConsumeOk(String consumerTag) { } + + @Override + public void handleCancelOk(String consumerTag) { } + + @Override + public void handleCancel(String consumerTag) throws IOException { + cancelCallback.handle(consumerTag); + } + + @Override + public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { } + + @Override + public void handleRecoverOk(String consumerTag) { } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + deliverCallback.handle(consumerTag, new Delivery(envelope, properties, body)); + } + }; + } + + private Consumer consumerFromDeliverShutdownCallbacks(final DeliverCallback deliverCallback, final ConsumerShutdownSignalCallback shutdownSignalCallback) { + return new Consumer() { + @Override + public void handleConsumeOk(String consumerTag) { } + + @Override + public void handleCancelOk(String consumerTag) { } + + @Override + public void handleCancel(String consumerTag) throws IOException { } + + @Override + public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { + shutdownSignalCallback.handleShutdownSignal(consumerTag, sig); + } + + @Override + public void handleRecoverOk(String consumerTag) { } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + deliverCallback.handle(consumerTag, new Delivery(envelope, properties, body)); + } + }; + } + + private Consumer consumerFromDeliverCancelShutdownCallbacks(final DeliverCallback deliverCallback, final CancelCallback cancelCallback, final ConsumerShutdownSignalCallback shutdownSignalCallback) { + return new Consumer() { + @Override + public void handleConsumeOk(String consumerTag) { } + + @Override + public void handleCancelOk(String consumerTag) { } + + @Override + public void handleCancel(String consumerTag) throws IOException { + cancelCallback.handle(consumerTag); + } + + @Override + public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { + shutdownSignalCallback.handleShutdownSignal(consumerTag, sig); + } + + @Override + public void handleRecoverOk(String consumerTag) { } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + deliverCallback.handle(consumerTag, new Delivery(envelope, properties, body)); + } + }; + } + + @Override + public void basicCancel(String consumerTag) throws IOException { + this.deleteRecordedConsumer(consumerTag); + delegate.basicCancel(consumerTag); + } + + @Override + public AMQP.Basic.RecoverOk basicRecover() throws IOException { + return delegate.basicRecover(); + } + + @Override + public AMQP.Basic.RecoverOk basicRecover(boolean requeue) throws IOException { + return delegate.basicRecover(requeue); + } + + @Override + public AMQP.Tx.SelectOk txSelect() throws IOException { + this.usesTransactions = true; + return delegate.txSelect(); + } + + @Override + public AMQP.Tx.CommitOk txCommit() throws IOException { + return delegate.txCommit(); + } + + @Override + public AMQP.Tx.RollbackOk txRollback() throws IOException { + return delegate.txRollback(); + } + + @Override + public AMQP.Confirm.SelectOk confirmSelect() throws IOException { + this.usesPublisherConfirms = true; + return delegate.confirmSelect(); + } + + @Override + public long getNextPublishSeqNo() { + return delegate.getNextPublishSeqNo(); + } + + @Override + public boolean waitForConfirms() throws InterruptedException { + return delegate.waitForConfirms(); + } + + @Override + public boolean waitForConfirms(long timeout) throws InterruptedException, TimeoutException { + return delegate.waitForConfirms(timeout); + } + + @Override + public void waitForConfirmsOrDie() throws IOException, InterruptedException { + delegate.waitForConfirmsOrDie(); + } + + @Override + public void waitForConfirmsOrDie(long timeout) throws IOException, InterruptedException, TimeoutException { + delegate.waitForConfirmsOrDie(timeout); + } + + @Override + public void asyncRpc(Method method) throws IOException { + delegate.asyncRpc(method); + } + + @Override + public Command rpc(Method method) throws IOException { + recordOnRpcRequest(method); + AMQCommand response = delegate.rpc(method); + recordOnRpcResponse(response.getMethod(), method); + return response; + } + + /** + * @see Connection#addShutdownListener(com.rabbitmq.client.ShutdownListener) + */ + @Override + public void addShutdownListener(ShutdownListener listener) { + this.shutdownHooks.add(listener); + delegate.addShutdownListener(listener); + } + + @Override + public void removeShutdownListener(ShutdownListener listener) { + this.shutdownHooks.remove(listener); + delegate.removeShutdownListener(listener); + } + + @Override + public ShutdownSignalException getCloseReason() { + return delegate.getCloseReason(); + } + + @Override + public void notifyListeners() { + delegate.notifyListeners(); + } + + @Override + public boolean isOpen() { + return delegate.isOpen(); + } + + @Override + public void addRecoveryListener(RecoveryListener listener) { + this.recoveryListeners.add(listener); + } + + @Override + public void removeRecoveryListener(RecoveryListener listener) { + this.recoveryListeners.remove(listener); + } + + // + // Recovery + // + + public void automaticallyRecover(AutorecoveringConnection connection, Connection connDelegate) throws IOException { + RecoveryAwareChannelN defunctChannel = this.delegate; + this.connection = connection; + + final RecoveryAwareChannelN newChannel = (RecoveryAwareChannelN) connDelegate.createChannel(this.getChannelNumber()); + // No Sonar: the channel could be null + if (newChannel == null) //NOSONAR + throw new IOException("Failed to create new channel for channel number=" + this.getChannelNumber() + " during recovery"); + newChannel.inheritOffsetFrom(defunctChannel); + this.delegate = newChannel; + + this.notifyRecoveryListenersStarted(); + this.recoverShutdownListeners(); + this.recoverReturnListeners(); + this.recoverConfirmListeners(); + this.recoverState(); + this.notifyRecoveryListenersComplete(); + } + + private void recoverShutdownListeners() { + for (ShutdownListener sh : this.shutdownHooks) { + this.delegate.addShutdownListener(sh); + } + } + + private void recoverReturnListeners() { + for(ReturnListener rl : this.returnListeners) { + this.delegate.addReturnListener(rl); + } + } + + private void recoverConfirmListeners() { + for(ConfirmListener cl : this.confirmListeners) { + this.delegate.addConfirmListener(cl); + } + } + + private void recoverState() throws IOException { + if (this.prefetchCountConsumer != 0) { + basicQos(this.prefetchCountConsumer, false); + } + if (this.prefetchCountGlobal != 0) { + basicQos(this.prefetchCountGlobal, true); + } + if(this.usesPublisherConfirms) { + this.confirmSelect(); + } + if(this.usesTransactions) { + this.txSelect(); + } + } + + private void notifyRecoveryListenersComplete() { + for (RecoveryListener f : this.recoveryListeners) { + f.handleRecovery(this); + } + } + + private void notifyRecoveryListenersStarted() { + for (RecoveryListener f : this.recoveryListeners) { + f.handleRecoveryStarted(this); + } + } + + private void recordQueueBinding(String queue, String exchange, String routingKey, Map arguments) { + this.connection.recordQueueBinding(this, queue, exchange, routingKey, arguments); + } + + private boolean deleteRecordedQueueBinding(String queue, String exchange, String routingKey, Map arguments) { + return this.connection.deleteRecordedQueueBinding(this, queue, exchange, routingKey, arguments); + } + + private void recordExchangeBinding(String destination, String source, String routingKey, Map arguments) { + this.connection.recordExchangeBinding(this, destination, source, routingKey, arguments); + } + + private boolean deleteRecordedExchangeBinding(String destination, String source, String routingKey, Map arguments) { + return this.connection.deleteRecordedExchangeBinding(this, destination, source, routingKey, arguments); + } + + private void recordQueue(AMQP.Queue.DeclareOk ok, String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments) { + RecordedQueue q = new RecordedQueue(this, ok.getQueue()). + durable(durable). + exclusive(exclusive). + autoDelete(autoDelete). + arguments(arguments). + recoveredQueueNameSupplier(connection.getRecoveredQueueNameSupplier()); + if (queue.equals(RecordedQueue.EMPTY_STRING)) { + q.serverNamed(true); + } + recordQueue(ok, q); + } + + private void recordQueue(AMQP.Queue.DeclareOk ok, RecordedQueue q) { + this.connection.recordQueue(ok, q); + } + + private void recordQueue(String queue, RecordedQueue meta) { + this.connection.recordQueue(queue, meta); + } + + private void deleteRecordedQueue(String queue) { + this.connection.deleteRecordedQueue(queue); + } + + private void recordExchange(AMQP.Exchange.DeclareOk ok, String exchange, String type, boolean durable, boolean autoDelete, Map arguments) { + RecordedExchange x = new RecordedExchange(this, exchange). + type(type). + durable(durable). + autoDelete(autoDelete). + arguments(arguments); + recordExchange(exchange, x); + } + + private void recordExchange(String exchange, RecordedExchange x) { + this.connection.recordExchange(exchange, x); + } + + private void deleteRecordedExchange(String exchange) { + this.connection.deleteRecordedExchange(exchange); + } + + private void recordConsumer(String consumerTag, + String queue, + boolean autoAck, + boolean exclusive, + Map arguments, + Consumer callback) { + RecordedConsumer consumer = new RecordedConsumer(this, queue). + autoAck(autoAck). + consumerTag(consumerTag). + exclusive(exclusive). + arguments(arguments). + consumer(callback); + this.consumerTags.add(consumerTag); + this.connection.recordConsumer(consumerTag, consumer); + } + + /** + * Delete the recorded consumer from this channel and accompanying connection + * @param consumerTag consumer tag to delete + */ + public void deleteRecordedConsumer(String consumerTag) { + this.consumerTags.remove(consumerTag); + RecordedConsumer c = this.connection.deleteRecordedConsumer(consumerTag); + if (c != null) { + this.connection.maybeDeleteRecordedAutoDeleteQueue(c.getQueue()); + } + } + + private void maybeDeleteRecordedAutoDeleteExchange(String exchange) { + this.connection.maybeDeleteRecordedAutoDeleteExchange(exchange); + } + + void updateConsumerTag(String tag, String newTag) { + synchronized (this.consumerTags) { + consumerTags.remove(tag); + consumerTags.add(newTag); + } + } + + @Override + public CompletableFuture asyncCompletableRpc(Method method) throws IOException { + recordOnRpcRequest(method); + CompletableFuture future = this.delegate.asyncCompletableRpc(method); + future.thenAccept(command -> { + if (command != null) { + recordOnRpcResponse(command.getMethod(), method); + } + }); + return future; + } + + private void recordOnRpcRequest(Method method) { + if (method instanceof AMQP.Queue.Delete) { + deleteRecordedQueue(((AMQP.Queue.Delete) method).getQueue()); + } else if (method instanceof AMQP.Exchange.Delete) { + deleteRecordedExchange(((AMQP.Exchange.Delete) method).getExchange()); + } else if (method instanceof AMQP.Queue.Unbind) { + AMQP.Queue.Unbind unbind = (AMQP.Queue.Unbind) method; + deleteRecordedQueueBinding( + unbind.getQueue(), unbind.getExchange(), + unbind.getRoutingKey(), unbind.getArguments() + ); + this.maybeDeleteRecordedAutoDeleteExchange(unbind.getExchange()); + } else if (method instanceof AMQP.Exchange.Unbind) { + AMQP.Exchange.Unbind unbind = (AMQP.Exchange.Unbind) method; + deleteRecordedExchangeBinding( + unbind.getDestination(), unbind.getSource(), + unbind.getRoutingKey(), unbind.getArguments() + ); + this.maybeDeleteRecordedAutoDeleteExchange(unbind.getSource()); + } + } + + private void recordOnRpcResponse(Method response, Method request) { + if (response instanceof AMQP.Queue.DeclareOk) { + if (request instanceof AMQP.Queue.Declare) { + AMQP.Queue.DeclareOk ok = (AMQP.Queue.DeclareOk) response; + AMQP.Queue.Declare declare = (AMQP.Queue.Declare) request; + recordQueue( + ok, declare.getQueue(), + declare.getDurable(), declare.getExclusive(), declare.getAutoDelete(), + declare.getArguments() + ); + } else { + LOGGER.warn("RPC response {} and RPC request {} not compatible, topology not recorded.", + response.getClass(), request.getClass()); + } + } else if (response instanceof AMQP.Exchange.DeclareOk) { + if (request instanceof AMQP.Exchange.Declare) { + AMQP.Exchange.DeclareOk ok = (AMQP.Exchange.DeclareOk) response; + AMQP.Exchange.Declare declare = (AMQP.Exchange.Declare) request; + recordExchange( + ok, declare.getExchange(), declare.getType(), + declare.getDurable(), declare.getAutoDelete(), + declare.getArguments() + ); + } else { + LOGGER.warn("RPC response {} and RPC request {} not compatible, topology not recorded.", + response.getClass(), request.getClass()); + } + } else if (response instanceof AMQP.Queue.BindOk) { + if (request instanceof AMQP.Queue.Bind) { + AMQP.Queue.Bind bind = (AMQP.Queue.Bind) request; + recordQueueBinding(bind.getQueue(), bind.getExchange(), bind.getRoutingKey(), bind.getArguments()); + } else { + LOGGER.warn("RPC response {} and RPC request {} not compatible, topology not recorded.", + response.getClass(), request.getClass()); + } + } else if (response instanceof AMQP.Exchange.BindOk) { + if (request instanceof AMQP.Exchange.Bind) { + AMQP.Exchange.Bind bind = (AMQP.Exchange.Bind) request; + recordExchangeBinding(bind.getDestination(), bind.getSource(), bind.getRoutingKey(), bind.getArguments()); + } else { + LOGGER.warn("RPC response {} and RPC request {} not compatible, topology not recorded.", + response.getClass(), request.getClass()); + } + } + } + + @Override + public String toString() { + return this.delegate.toString(); + } + +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringConnection.java b/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringConnection.java new file mode 100644 index 0000000000..0e3e82d95a --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/AutorecoveringConnection.java @@ -0,0 +1,1220 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.ConnectionParams; +import com.rabbitmq.client.impl.FrameHandlerFactory; +import com.rabbitmq.client.impl.NetworkConnection; +import com.rabbitmq.client.observation.ObservationCollector; +import com.rabbitmq.utility.Utility; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Predicate; + +/** + * Connection implementation that performs automatic recovery when + * connection shutdown is not initiated by the application (e.g. due to + * an I/O exception). + * + * Topology (exchanges, queues, bindings, and consumers) can be (and by default is) recovered + * as well, in this order: + * + *

    + *
  1. Exchanges
  2. + *
  3. Queues
  4. + *
  5. Bindings (both queue and exchange-to-exchange)
  6. + *
  7. Consumers
  8. + *
+ * + * @see com.rabbitmq.client.Connection + * @see com.rabbitmq.client.Recoverable + * @see com.rabbitmq.client.ConnectionFactory#setAutomaticRecoveryEnabled(boolean) + * @see com.rabbitmq.client.ConnectionFactory#setTopologyRecoveryEnabled(boolean) + * @since 3.3.0 + */ +public class AutorecoveringConnection implements RecoverableConnection, NetworkConnection { + + public static final Predicate DEFAULT_CONNECTION_RECOVERY_TRIGGERING_CONDITION = + cause -> !cause.isInitiatedByApplication() || (cause.getCause() instanceof MissedHeartbeatException); + + private static final Logger LOGGER = LoggerFactory.getLogger(AutorecoveringConnection.class); + + private final RecoveryAwareAMQConnectionFactory cf; + private final Map channels; + private final ConnectionParams params; + private volatile RecoveryAwareAMQConnection delegate; + + private final List shutdownHooks = Collections.synchronizedList(new ArrayList<>()); + private final List recoveryListeners = Collections.synchronizedList(new ArrayList<>()); + private final List blockedListeners = Collections.synchronizedList(new ArrayList<>()); + + // Records topology changes + private final Map recordedQueues = Collections.synchronizedMap(new LinkedHashMap<>()); + private final List recordedBindings = Collections.synchronizedList(new ArrayList<>()); + private final Map recordedExchanges = Collections.synchronizedMap(new LinkedHashMap<>()); + private final Map consumers = Collections.synchronizedMap(new LinkedHashMap<>()); + private final List consumerRecoveryListeners = Collections.synchronizedList(new ArrayList<>()); + private final List queueRecoveryListeners = Collections.synchronizedList(new ArrayList<>()); + + private final TopologyRecoveryFilter topologyRecoveryFilter; + + // Used to block connection recovery attempts after close() is invoked. + private volatile boolean manuallyClosed = false; + + // This lock guards the manuallyClosed flag and the delegate connection. Guarding these two ensures that a new connection can never + // be created after application code has initiated shutdown. + private final Object recoveryLock = new Object(); + + private final Predicate connectionRecoveryTriggeringCondition; + + private final RetryHandler retryHandler; + private final RecoveredQueueNameSupplier recoveredQueueNameSupplier; + + public AutorecoveringConnection(ConnectionParams params, FrameHandlerFactory f, List
addrs) { + this(params, f, new ListAddressResolver(addrs)); + } + + public AutorecoveringConnection(ConnectionParams params, FrameHandlerFactory f, AddressResolver addressResolver) { + this(params, f, addressResolver, new NoOpMetricsCollector(), ObservationCollector.NO_OP); + } + + public AutorecoveringConnection(ConnectionParams params, FrameHandlerFactory f, AddressResolver addressResolver, + MetricsCollector metricsCollector, ObservationCollector observationCollector) { + this.cf = new RecoveryAwareAMQConnectionFactory( + params, f, addressResolver, + metricsCollector, observationCollector + ); + this.params = params; + + this.connectionRecoveryTriggeringCondition = params.getConnectionRecoveryTriggeringCondition() == null ? + DEFAULT_CONNECTION_RECOVERY_TRIGGERING_CONDITION : params.getConnectionRecoveryTriggeringCondition(); + + setupErrorOnWriteListenerForPotentialRecovery(); + + this.channels = new ConcurrentHashMap<>(); + this.topologyRecoveryFilter = params.getTopologyRecoveryFilter() == null ? + letAllPassFilter() : params.getTopologyRecoveryFilter(); + + this.retryHandler = params.getTopologyRecoveryRetryHandler(); + this.recoveredQueueNameSupplier = params.getRecoveredQueueNameSupplier() == null ? + RecordedQueue.DEFAULT_QUEUE_NAME_SUPPLIER : params.getRecoveredQueueNameSupplier(); + } + + private void setupErrorOnWriteListenerForPotentialRecovery() { + final ThreadFactory threadFactory = this.params.getThreadFactory(); + final Lock errorOnWriteLock = new ReentrantLock(); + this.params.setErrorOnWriteListener((connection, exception) -> { + // this is called for any write error + // we should trigger the error handling and the recovery only once + if (errorOnWriteLock.tryLock()) { + try { + Thread recoveryThread = threadFactory.newThread(() -> { + AMQConnection c = (AMQConnection) connection; + c.handleIoError(exception); + }); + recoveryThread.setName("RabbitMQ Error On Write Thread"); + recoveryThread.start(); + } finally { + errorOnWriteLock.unlock(); + } + } + throw exception; + }); + } + + private static TopologyRecoveryFilter letAllPassFilter() { + return new TopologyRecoveryFilter() {}; + } + + /** + * Private API. + * @throws IOException + * @see com.rabbitmq.client.ConnectionFactory#newConnection(java.util.concurrent.ExecutorService) + */ + public void init() throws IOException, TimeoutException { + this.delegate = this.cf.newConnection(); + this.addAutomaticRecoveryListener(delegate); + } + + /** + * @see com.rabbitmq.client.Connection#createChannel() + */ + @Override + public Channel createChannel() throws IOException { + RecoveryAwareChannelN ch = (RecoveryAwareChannelN) delegate.createChannel(); + // No Sonar: the channel could be null + if (ch == null) { //NOSONAR + return null; + } else { + return this.wrapChannel(ch); + } + } + + /** + * @see com.rabbitmq.client.Connection#createChannel(int) + */ + @Override + public Channel createChannel(int channelNumber) throws IOException { + RecoveryAwareChannelN ch = (RecoveryAwareChannelN) delegate.createChannel(channelNumber); + // No Sonar: the channel could be null + if (ch == null) { //NOSONAR + return null; + } else { + return this.wrapChannel(ch); + } + } + + /** + * Creates a recovering channel from a regular channel and registers it. + * If the regular channel cannot be created (e.g. too many channels are open + * already), returns null. + * + * @param delegateChannel Channel to wrap. + * @return Recovering channel. + */ + private Channel wrapChannel(RecoveryAwareChannelN delegateChannel) { + if (delegateChannel == null) { + return null; + } else { + final AutorecoveringChannel channel = new AutorecoveringChannel(this, delegateChannel); + this.registerChannel(channel); + return channel; + } + } + + void registerChannel(AutorecoveringChannel channel) { + this.channels.put(channel.getChannelNumber(), channel); + } + + void unregisterChannel(AutorecoveringChannel channel) { + this.channels.remove(channel.getChannelNumber()); + } + + /** + * @see com.rabbitmq.client.Connection#getServerProperties() + */ + @Override + public Map getServerProperties() { + return delegate.getServerProperties(); + } + + /** + * @see com.rabbitmq.client.Connection#getClientProperties() + */ + @Override + public Map getClientProperties() { + return delegate.getClientProperties(); + } + + /** + * @see com.rabbitmq.client.Connection#getClientProvidedName() + * @see ConnectionFactory#newConnection(Address[], String) + * @see ConnectionFactory#newConnection(ExecutorService, Address[], String) + */ + @Override + public String getClientProvidedName() { + return delegate.getClientProvidedName(); + } + + /** + * @see com.rabbitmq.client.Connection#getFrameMax() + */ + @Override + public int getFrameMax() { + return delegate.getFrameMax(); + } + + /** + * @see com.rabbitmq.client.Connection#getHeartbeat() + */ + @Override + public int getHeartbeat() { + return delegate.getHeartbeat(); + } + + /** + * @see com.rabbitmq.client.Connection#getChannelMax() + */ + @Override + public int getChannelMax() { + return delegate.getChannelMax(); + } + + /** + * @see com.rabbitmq.client.Connection#isOpen() + */ + @Override + public boolean isOpen() { + return delegate.isOpen(); + } + + /** + * @see com.rabbitmq.client.Connection#close() + */ + @Override + public void close() throws IOException { + synchronized(recoveryLock) { + this.manuallyClosed = true; + } + delegate.close(); + } + + /** + * @see Connection#close(int) + */ + @Override + public void close(int timeout) throws IOException { + synchronized(recoveryLock) { + this.manuallyClosed = true; + } + delegate.close(timeout); + } + + /** + * @see Connection#close(int, String, int) + */ + @Override + public void close(int closeCode, String closeMessage, int timeout) throws IOException { + synchronized(recoveryLock) { + this.manuallyClosed = true; + } + delegate.close(closeCode, closeMessage, timeout); + } + + /** + * @see com.rabbitmq.client.Connection#abort() + */ + @Override + public void abort() { + synchronized(recoveryLock) { + this.manuallyClosed = true; + } + delegate.abort(); + } + + /** + * @see Connection#abort(int, String, int) + */ + @Override + public void abort(int closeCode, String closeMessage, int timeout) { + synchronized(recoveryLock) { + this.manuallyClosed = true; + } + delegate.abort(closeCode, closeMessage, timeout); + } + + /** + * @see Connection#abort(int, String) + */ + @Override + public void abort(int closeCode, String closeMessage) { + synchronized(recoveryLock) { + this.manuallyClosed = true; + } + delegate.abort(closeCode, closeMessage); + } + + /** + * @see Connection#abort(int) + */ + @Override + public void abort(int timeout) { + synchronized(recoveryLock) { + this.manuallyClosed = true; + } + delegate.abort(timeout); + } + + /** + * Not supposed to be used outside of automated tests. + */ + public AMQConnection getDelegate() { + return delegate; + } + + /** + * @see com.rabbitmq.client.Connection#getCloseReason() + */ + @Override + public ShutdownSignalException getCloseReason() { + return delegate.getCloseReason(); + } + + /** + * @see com.rabbitmq.client.ShutdownNotifier#addShutdownListener(com.rabbitmq.client.ShutdownListener) + */ + @Override + public void addBlockedListener(BlockedListener listener) { + this.blockedListeners.add(listener); + delegate.addBlockedListener(listener); + } + + @Override + public BlockedListener addBlockedListener(BlockedCallback blockedCallback, UnblockedCallback unblockedCallback) { + BlockedListener blockedListener = new BlockedListener() { + + @Override + public void handleBlocked(String reason) throws IOException { + blockedCallback.handle(reason); + } + + @Override + public void handleUnblocked() throws IOException { + unblockedCallback.handle(); + } + }; + this.addBlockedListener(blockedListener); + return blockedListener; + } + + /** + * @see Connection#removeBlockedListener(com.rabbitmq.client.BlockedListener) + */ + @Override + public boolean removeBlockedListener(BlockedListener listener) { + this.blockedListeners.remove(listener); + return delegate.removeBlockedListener(listener); + } + + /** + * @see com.rabbitmq.client.Connection#clearBlockedListeners() + */ + @Override + public void clearBlockedListeners() { + this.blockedListeners.clear(); + delegate.clearBlockedListeners(); + } + + /** + * @see com.rabbitmq.client.Connection#close(int, String) + */ + @Override + public void close(int closeCode, String closeMessage) throws IOException { + synchronized(recoveryLock) { + this.manuallyClosed = true; + } + delegate.close(closeCode, closeMessage); + } + + /** + * @see Connection#addShutdownListener(com.rabbitmq.client.ShutdownListener) + */ + @Override + public void addShutdownListener(ShutdownListener listener) { + this.shutdownHooks.add(listener); + delegate.addShutdownListener(listener); + } + + /** + * @see com.rabbitmq.client.ShutdownNotifier#removeShutdownListener(com.rabbitmq.client.ShutdownListener) + */ + @Override + public void removeShutdownListener(ShutdownListener listener) { + this.shutdownHooks.remove(listener); + delegate.removeShutdownListener(listener); + } + + /** + * @see com.rabbitmq.client.ShutdownNotifier#notifyListeners() + */ + @Override + public void notifyListeners() { + delegate.notifyListeners(); + } + + /** + * Adds the recovery listener + * @param listener {@link com.rabbitmq.client.RecoveryListener} to execute after this connection recovers from network failure + */ + @Override + public void addRecoveryListener(RecoveryListener listener) { + this.recoveryListeners.add(listener); + } + + /** + * Removes the recovery listener + * @param listener {@link com.rabbitmq.client.RecoveryListener} to remove + */ + @Override + public void removeRecoveryListener(RecoveryListener listener) { + this.recoveryListeners.remove(listener); + } + + /** + * @see com.rabbitmq.client.impl.AMQConnection#getExceptionHandler() + */ + @Override + public ExceptionHandler getExceptionHandler() { + return this.delegate.getExceptionHandler(); + } + + /** + * @see com.rabbitmq.client.Connection#getPort() + */ + @Override + public int getPort() { + return delegate.getPort(); + } + + /** + * @see com.rabbitmq.client.Connection#getAddress() + */ + @Override + public InetAddress getAddress() { + return delegate.getAddress(); + } + + /** + * @return client socket address + */ + @Override + public InetAddress getLocalAddress() { + return this.delegate.getLocalAddress(); + } + + /** + * @return client socket port + */ + @Override + public int getLocalPort() { + return this.delegate.getLocalPort(); + } + + // + // Recovery + // + + private void addAutomaticRecoveryListener(final RecoveryAwareAMQConnection newConn) { + final AutorecoveringConnection c = this; + // this listener will run after shutdown listeners, + // see https://github.com/rabbitmq/rabbitmq-java-client/issues/135 + RecoveryCanBeginListener starter = cause -> { + try { + if (shouldTriggerConnectionRecovery(cause)) { + c.beginAutomaticRecovery(); + } + } catch (Exception e) { + newConn.getExceptionHandler().handleConnectionRecoveryException(c, e); + } + }; + synchronized (this) { + newConn.addRecoveryCanBeginListener(starter); + } + } + + protected boolean shouldTriggerConnectionRecovery(ShutdownSignalException cause) { + return connectionRecoveryTriggeringCondition.test(cause); + } + + /** + * Not part of the public API. Mean to be used by JVM RabbitMQ clients that build on + * top of the Java client and need to be notified when server-named queue name changes + * after recovery. + * + * @param listener listener that observes queue name changes after recovery + */ + public void addQueueRecoveryListener(QueueRecoveryListener listener) { + this.queueRecoveryListeners.add(listener); + } + + /** + * @see com.rabbitmq.client.impl.recovery.AutorecoveringConnection#addQueueRecoveryListener + * @param listener listener to be removed + */ + public void removeQueueRecoveryListener(QueueRecoveryListener listener) { + this.queueRecoveryListeners.remove(listener); + } + + /** + * Not part of the public API. Mean to be used by JVM RabbitMQ clients that build on + * top of the Java client and need to be notified when consumer tag changes + * after recovery. + * + * @param listener listener that observes consumer tag changes after recovery + */ + public void addConsumerRecoveryListener(ConsumerRecoveryListener listener) { + this.consumerRecoveryListeners.add(listener); + } + + /** + * @see com.rabbitmq.client.impl.recovery.AutorecoveringConnection#addConsumerRecoveryListener(ConsumerRecoveryListener) + * @param listener listener to be removed + */ + public void removeConsumerRecoveryListener(ConsumerRecoveryListener listener) { + this.consumerRecoveryListeners.remove(listener); + } + + RecoveredQueueNameSupplier getRecoveredQueueNameSupplier() { + return this.recoveredQueueNameSupplier; + } + + private synchronized void beginAutomaticRecovery() throws InterruptedException { + final long delay = this.params.getRecoveryDelayHandler().getDelay(0); + if (delay > 0) { + this.wait(delay); + } + + this.notifyRecoveryListenersStarted(); + + final RecoveryAwareAMQConnection newConn = this.recoverConnection(); + if (newConn == null) { + return; + } + LOGGER.debug("Connection {} has recovered", newConn); + this.addAutomaticRecoveryListener(newConn); + this.recoverShutdownListeners(newConn); + this.recoverBlockedListeners(newConn); + this.recoverChannels(newConn); + // don't assign new delegate connection until channel recovery is complete + this.delegate = newConn; + if (this.params.isTopologyRecoveryEnabled()) { + notifyTopologyRecoveryListenersStarted(); + recoverTopology(params.getTopologyRecoveryExecutor()); + } + this.notifyRecoveryListenersComplete(); + } + + private void recoverShutdownListeners(final RecoveryAwareAMQConnection newConn) { + for (ShutdownListener sh : Utility.copy(this.shutdownHooks)) { + newConn.addShutdownListener(sh); + } + } + + private void recoverBlockedListeners(final RecoveryAwareAMQConnection newConn) { + for (BlockedListener bl : Utility.copy(this.blockedListeners)) { + newConn.addBlockedListener(bl); + } + } + + // Returns new connection if the connection was recovered, + // null if application initiated shutdown while attempting recovery. + private RecoveryAwareAMQConnection recoverConnection() throws InterruptedException { + int attempts = 0; + while (!manuallyClosed) { + try { + attempts++; + // No Sonar: no need to close this resource because we're the one that creates it + // and hands it over to the user + RecoveryAwareAMQConnection newConn = this.cf.newConnection(); //NOSONAR + synchronized(recoveryLock) { + if (!manuallyClosed) { + // This is the standard case. + return newConn; + } + } + // This is the once in a blue moon case. + // Application code just called close as the connection + // was being re-established. So we attempt to close the newly created connection. + newConn.abort(); + return null; + } catch (Exception e) { + Thread.sleep(this.params.getRecoveryDelayHandler().getDelay(attempts)); + this.getExceptionHandler().handleConnectionRecoveryException(this, e); + } + } + + return null; + } + + private void recoverChannels(final RecoveryAwareAMQConnection newConn) { + for (AutorecoveringChannel ch : this.channels.values()) { + try { + ch.automaticallyRecover(this, newConn); + LOGGER.debug("Channel {} has recovered", ch); + } catch (Throwable t) { + newConn.getExceptionHandler().handleChannelRecoveryException(ch, t); + } + } + } + + public void recoverChannel(AutorecoveringChannel channel) throws IOException { + channel.automaticallyRecover(this, this.delegate); + } + + private void notifyRecoveryListenersComplete() { + for (RecoveryListener f : Utility.copy(this.recoveryListeners)) { + f.handleRecovery(this); + } + } + + private void notifyRecoveryListenersStarted() { + for (RecoveryListener f : Utility.copy(this.recoveryListeners)) { + f.handleRecoveryStarted(this); + } + } + + private void notifyTopologyRecoveryListenersStarted() { + for (RecoveryListener f : Utility.copy(this.recoveryListeners)) { + f.handleTopologyRecoveryStarted(this); + } + } + + /** + * Recover a closed channel and all topology (i.e. RecordedEntities) associated to it. + * Any errors will be sent to the {@link #getExceptionHandler()}. + * @param channel channel to recover + * @throws IllegalArgumentException if this channel is not owned by this connection + */ + public void recoverChannelAndTopology(final AutorecoveringChannel channel) { + if (!channels.containsValue(channel)) { + throw new IllegalArgumentException("This channel is not owned by this connection"); + } + try { + LOGGER.debug("Recovering channel={}", channel); + recoverChannel(channel); + LOGGER.debug("Recovered channel={}. Now recovering its topology", channel); + Utility.copy(recordedExchanges).values().stream() + .filter(e -> e.getChannel() == channel) + .forEach(e -> recoverExchange(e, false)); + Utility.copy(recordedQueues).values().stream() + .filter(q -> q.getChannel() == channel) + .forEach(q -> recoverQueue(q.getName(), q, false)); + Utility.copy(recordedBindings).stream() + .filter(b -> b.getChannel() == channel) + .forEach(b -> recoverBinding(b, false)); + Utility.copy(consumers).values().stream() + .filter(c -> c.getChannel() == channel) + .forEach(c -> recoverConsumer(c.getConsumerTag(), c, false)); + LOGGER.debug("Recovered topology for channel={}", channel); + } catch (Exception e) { + getExceptionHandler().handleChannelRecoveryException(channel, e); + } + } + + private void recoverTopology(final ExecutorService executor) { + // The recovery sequence is the following: + // 1. Recover exchanges + // 2. Recover queues + // 3. Recover bindings + // 4. Recover consumers + if (executor == null) { + // recover entities in serial on the main connection thread + for (final RecordedExchange exchange : Utility.copy(recordedExchanges).values()) { + recoverExchange(exchange, true); + } + for (final Map.Entry entry : Utility.copy(recordedQueues).entrySet()) { + recoverQueue(entry.getKey(), entry.getValue(), true); + } + for (final RecordedBinding b : Utility.copy(recordedBindings)) { + recoverBinding(b, true); + } + for (final Map.Entry entry : Utility.copy(consumers).entrySet()) { + recoverConsumer(entry.getKey(), entry.getValue(), true); + } + } else { + // Support recovering entities in parallel for connections that have a lot of queues, bindings, & consumers + // A channel is single threaded, so group things by channel and recover 1 entity at a time per channel + // We also need to recover 1 type of entity at a time in case channel1 has a binding to a queue that is currently owned and being recovered by channel2 for example + // Note: invokeAll will block until all callables are completed and all returned futures will be complete + try { + recoverEntitiesAsynchronously(executor, Utility.copy(recordedExchanges).values()); + recoverEntitiesAsynchronously(executor, Utility.copy(recordedQueues).values()); + recoverEntitiesAsynchronously(executor, Utility.copy(recordedBindings)); + recoverEntitiesAsynchronously(executor, Utility.copy(consumers).values()); + } catch (final Exception cause) { + final String message = "Caught an exception while recovering topology: " + cause.getMessage(); + final TopologyRecoveryException e = new TopologyRecoveryException(message, cause); + getExceptionHandler().handleTopologyRecoveryException(delegate, null, e); + } + } + } + + public void recoverExchange(RecordedExchange x, boolean retry) { + // recorded exchanges are guaranteed to be non-predefined (we filter out predefined ones in exchangeDeclare). MK. + try { + if (topologyRecoveryFilter.filterExchange(x)) { + if (retry) { + final RecordedExchange entity = x; + x = (RecordedExchange) wrapRetryIfNecessary(x, () -> { + entity.recover(); + return null; + }).getRecordedEntity(); + } else { + x.recover(); + } + LOGGER.debug("{} has recovered", x); + } + } catch (Exception cause) { + final String message = "Caught an exception while recovering exchange " + x.getName() + + ": " + cause.getMessage(); + TopologyRecoveryException e = new TopologyRecoveryException(message, cause, x); + this.getExceptionHandler().handleTopologyRecoveryException(delegate, x.getDelegateChannel(), e); + } + } + + /** + * Recover the queue. Any exceptions during recovery will be delivered to the connection's {@link ExceptionHandler}. + * @param oldName queue name + * @param q recorded queue + * @param retry whether to retry the recovery if an error occurs and a RetryHandler was configured on the connection + */ + public void recoverQueue(final String oldName, RecordedQueue q, boolean retry) { + try { + internalRecoverQueue(oldName, q, retry); + } catch (Exception cause) { + final String message = "Caught an exception while recovering queue " + oldName + + ": " + cause.getMessage(); + TopologyRecoveryException e = new TopologyRecoveryException(message, cause, q); + this.getExceptionHandler().handleTopologyRecoveryException(delegate, q.getDelegateChannel(), e); + } + } + + /** + * Recover the queue. Errors are not retried and not delivered to the connection's {@link ExceptionHandler} + * @param oldName queue name + * @param q recorded queue + * @throws Exception if an error occurs recovering the queue + */ + void recoverQueue(final String oldName, RecordedQueue q) throws Exception { + internalRecoverQueue(oldName, q, false); + } + + private void internalRecoverQueue(final String oldName, RecordedQueue q, boolean retry) throws Exception { + if (topologyRecoveryFilter.filterQueue(q)) { + LOGGER.debug("Recovering {}", q); + if (retry) { + final RecordedQueue entity = q; + q = (RecordedQueue) wrapRetryIfNecessary(q, () -> { + entity.recover(); + return null; + }).getRecordedEntity(); + } else { + q.recover(); + } + String newName = q.getName(); + if (!oldName.equals(newName)) { + // make sure queues are re-added with + // their new names, if applicable. MK. + propagateQueueNameChangeToBindings(oldName, newName); + propagateQueueNameChangeToConsumers(oldName, newName); + synchronized (this.recordedQueues) { + // bug26552: + // remove old name after we've updated the bindings and consumers, + deleteRecordedQueue(oldName); + this.recordedQueues.put(newName, q); + } + } + for (QueueRecoveryListener qrl : Utility.copy(this.queueRecoveryListeners)) { + qrl.queueRecovered(oldName, newName); + } + LOGGER.debug("{} has recovered", q); + } + } + + public void recoverBinding(RecordedBinding b, boolean retry) { + try { + if (this.topologyRecoveryFilter.filterBinding(b)) { + if (retry) { + final RecordedBinding entity = b; + b = (RecordedBinding) wrapRetryIfNecessary(b, () -> { + entity.recover(); + return null; + }).getRecordedEntity(); + } else { + b.recover(); + } + LOGGER.debug("{} has recovered", b); + } + } catch (Exception cause) { + String message = "Caught an exception while recovering binding between " + b.getSource() + + " and " + b.getDestination() + ": " + cause.getMessage(); + TopologyRecoveryException e = new TopologyRecoveryException(message, cause, b); + this.getExceptionHandler().handleTopologyRecoveryException(delegate, b.getDelegateChannel(), e); + } + } + + /** + * Recover the consumer. Any exceptions during recovery will be delivered to the connection's {@link ExceptionHandler}. + * @param tag consumer tag + * @param consumer recorded consumer + * @param retry whether to retry the recovery if an error occurs and a RetryHandler was configured on the connection + */ + public void recoverConsumer(final String tag, RecordedConsumer consumer, boolean retry) { + try { + internalRecoverConsumer(tag, consumer, retry); + } catch (Exception cause) { + final String message = "Caught an exception while recovering consumer " + tag + + ": " + cause.getMessage(); + TopologyRecoveryException e = new TopologyRecoveryException(message, cause, consumer); + this.getExceptionHandler().handleTopologyRecoveryException(delegate, consumer.getDelegateChannel(), e); + } + } + + /** + * Recover the consumer. Errors are not retried and not delivered to the connection's {@link ExceptionHandler} + * @param tag consumer tag + * @param consumer recorded consumer + * @throws Exception if an error occurs recovering the consumer + */ + void recoverConsumer(final String tag, RecordedConsumer consumer) throws Exception { + internalRecoverConsumer(tag, consumer, false); + } + + private void internalRecoverConsumer(final String tag, RecordedConsumer consumer, boolean retry) throws Exception { + if (this.topologyRecoveryFilter.filterConsumer(consumer)) { + LOGGER.debug("Recovering {}", consumer); + String newTag = null; + if (retry) { + final RecordedConsumer entity = consumer; + RetryResult retryResult = wrapRetryIfNecessary(consumer, entity::recover); + consumer = (RecordedConsumer) retryResult.getRecordedEntity(); + newTag = (String) retryResult.getResult(); + } else { + newTag = consumer.recover(); + } + + // make sure server-generated tags are re-added. MK. + if(tag != null && !tag.equals(newTag)) { + synchronized (this.consumers) { + this.consumers.remove(tag); + this.consumers.put(newTag, consumer); + } + consumer.getChannel().updateConsumerTag(tag, newTag); + } + + for (ConsumerRecoveryListener crl : Utility.copy(this.consumerRecoveryListeners)) { + crl.consumerRecovered(tag, newTag); + } + LOGGER.debug("{} has recovered", consumer); + } + } + + private RetryResult wrapRetryIfNecessary(RecordedEntity entity, Callable recoveryAction) throws Exception { + if (this.retryHandler == null) { + T result = recoveryAction.call(); + return new RetryResult(entity, result); + } else { + try { + T result = recoveryAction.call(); + return new RetryResult(entity, result); + } catch (Exception e) { + RetryContext retryContext = new RetryContext(entity, e, this); + RetryResult retryResult; + if (entity instanceof RecordedQueue) { + retryResult = this.retryHandler.retryQueueRecovery(retryContext); + } else if (entity instanceof RecordedExchange) { + retryResult = this.retryHandler.retryExchangeRecovery(retryContext); + } else if (entity instanceof RecordedBinding) { + retryResult = this.retryHandler.retryBindingRecovery(retryContext); + } else if (entity instanceof RecordedConsumer) { + retryResult = this.retryHandler.retryConsumerRecovery(retryContext); + } else { + throw new IllegalArgumentException("Unknown type of recorded entity: " + entity); + } + return retryResult; + } + } + } + + + private void propagateQueueNameChangeToBindings(String oldName, String newName) { + for (RecordedBinding b : Utility.copy(this.recordedBindings)) { + if (b.getDestination().equals(oldName)) { + b.setDestination(newName); + } + } + } + + private void propagateQueueNameChangeToConsumers(String oldName, String newName) { + for (RecordedConsumer c : Utility.copy(this.consumers).values()) { + if (c.getQueue().equals(oldName)) { + c.setQueue(newName); + } + } + } + + private void recoverEntitiesAsynchronously(ExecutorService executor, Collection recordedEntities) throws InterruptedException { + List> tasks = executor.invokeAll(groupEntitiesByChannel(recordedEntities)); + for (Future task : tasks) { + if (!task.isDone()) { + LOGGER.warn("Recovery task should be done {}", task); + } else { + try { + task.get(1, TimeUnit.MILLISECONDS); + } catch (Exception e) { + LOGGER.warn("Recovery task is done but returned an exception", e); + } + } + } + } + + private List> groupEntitiesByChannel(final Collection entities) { + // map entities by channel + final Map> map = new LinkedHashMap<>(); + for (final E entity : entities) { + final AutorecoveringChannel channel = entity.getChannel(); + map.computeIfAbsent(channel, c -> new ArrayList<>()).add(entity); + } + // now create a runnable per channel + final List> callables = new ArrayList<>(); + for (final List entityList : map.values()) { + callables.add(Executors.callable(() -> { + for (final E entity : entityList) { + if (entity instanceof RecordedExchange) { + recoverExchange((RecordedExchange)entity, true); + } else if (entity instanceof RecordedQueue) { + final RecordedQueue q = (RecordedQueue) entity; + recoverQueue(q.getName(), q, true); + } else if (entity instanceof RecordedBinding) { + recoverBinding((RecordedBinding) entity, true); + } else if (entity instanceof RecordedConsumer) { + final RecordedConsumer c = (RecordedConsumer) entity; + recoverConsumer(c.getConsumerTag(), c, true); + } + } + })); + } + return callables; + } + + void recordQueueBinding(AutorecoveringChannel ch, + String queue, + String exchange, + String routingKey, + Map arguments) { + RecordedBinding binding = new RecordedQueueBinding(ch). + source(exchange). + destination(queue). + routingKey(routingKey). + arguments(arguments); + this.recordedBindings.remove(binding); + this.recordedBindings.add(binding); + } + + boolean deleteRecordedQueueBinding(AutorecoveringChannel ch, + String queue, + String exchange, + String routingKey, + Map arguments) { + RecordedBinding b = new RecordedQueueBinding(ch). + source(exchange). + destination(queue). + routingKey(routingKey). + arguments(arguments); + return this.recordedBindings.remove(b); + } + + void recordExchangeBinding(AutorecoveringChannel ch, + String destination, + String source, + String routingKey, + Map arguments) { + RecordedBinding binding = new RecordedExchangeBinding(ch). + source(source). + destination(destination). + routingKey(routingKey). + arguments(arguments); + this.recordedBindings.remove(binding); + this.recordedBindings.add(binding); + } + + boolean deleteRecordedExchangeBinding(AutorecoveringChannel ch, + String destination, + String source, + String routingKey, + Map arguments) { + RecordedBinding b = new RecordedExchangeBinding(ch). + source(source). + destination(destination). + routingKey(routingKey). + arguments(arguments); + return this.recordedBindings.remove(b); + } + + void recordQueue(AMQP.Queue.DeclareOk ok, RecordedQueue q) { + this.recordedQueues.put(ok.getQueue(), q); + } + + void recordQueue(String queue, RecordedQueue meta) { + this.recordedQueues.put(queue, meta); + } + + void deleteRecordedQueue(String queue) { + this.recordedQueues.remove(queue); + Set xs = this.removeBindingsWithDestination(queue); + for (RecordedBinding b : xs) { + this.maybeDeleteRecordedAutoDeleteExchange(b.getSource()); + } + } + + /** + * Exclude the queue from the list of queues to recover after connection failure. + * Intended to be used in scenarios where you want to remove the queue from this connection's recovery list but don't want to delete the queue from the server. + * For example, in tests. + * + * @param queue queue name to exclude from recorded recovery queues + * @param ifUnused if true, the RecordedQueue will only be excluded if no local consumers are using it. + */ + public void excludeQueueFromRecovery(final String queue, final boolean ifUnused) { + if (ifUnused) { + // Note: This is basically the same as maybeDeleteRecordedAutoDeleteQueue except it works for non auto-delete queues as well. + synchronized (this.consumers) { + synchronized (this.recordedQueues) { + if (!hasMoreConsumersOnQueue(this.consumers.values(), queue)) { + deleteRecordedQueue(queue); + } + } + } + } else { + deleteRecordedQueue(queue); + } + } + + void recordExchange(String exchange, RecordedExchange x) { + this.recordedExchanges.put(exchange, x); + } + + void deleteRecordedExchange(String exchange) { + this.recordedExchanges.remove(exchange); + Set xs1 = this.removeBindingsWithDestination(exchange); + for (RecordedBinding b : xs1) { + this.maybeDeleteRecordedAutoDeleteExchange(b.getSource()); + } + Set xs2 = this.removeBindingsWithSource(exchange); + for (RecordedBinding b : xs2) { + this.maybeDeleteRecordedAutoDeleteExchange(b.getSource()); + } + } + + void recordConsumer(String result, RecordedConsumer consumer) { + this.consumers.put(result, consumer); + } + + RecordedConsumer deleteRecordedConsumer(String consumerTag) { + return this.consumers.remove(consumerTag); + } + + void maybeDeleteRecordedAutoDeleteQueue(String queue) { + synchronized (this.consumers) { + synchronized (this.recordedQueues) { + if(!hasMoreConsumersOnQueue(this.consumers.values(), queue)) { + RecordedQueue q = this.recordedQueues.get(queue); + // the last consumer on this connection is gone, remove the recorded queue + // if it is auto-deleted + if(q != null && q.isAutoDelete()) { + deleteRecordedQueue(queue); + } + } + } + } + } + + void maybeDeleteRecordedAutoDeleteExchange(String exchange) { + synchronized (this.recordedExchanges) { + if(!hasMoreDestinationsBoundToExchange(Utility.copy(this.recordedBindings), exchange)) { + RecordedExchange x = this.recordedExchanges.get(exchange); + // the last binding where this exchange is the source is gone, remove the recorded exchange + // if it is auto-deleted + if(x != null && x.isAutoDelete()) { + deleteRecordedExchange(exchange); + } + } + } + } + + boolean hasMoreDestinationsBoundToExchange(List bindings, String exchange) { + boolean result = false; + for (RecordedBinding b : bindings) { + if(exchange.equals(b.getSource())) { + result = true; + break; + } + } + return result; + } + + boolean hasMoreConsumersOnQueue(Collection consumers, String queue) { + boolean result = false; + for (RecordedConsumer c : consumers) { + if(queue.equals(c.getQueue())) { + result = true; + break; + } + } + return result; + } + + Set removeBindingsWithDestination(String s) { + return this.removeBindingsWithCondition(b -> b.getDestination().equals(s)); + } + + Set removeBindingsWithSource(String s) { + return this.removeBindingsWithCondition(b -> b.getSource().equals(s)); + } + + private Set removeBindingsWithCondition(Predicate condition) { + final Set result = new LinkedHashSet<>(); + synchronized (this.recordedBindings) { + for (Iterator it = this.recordedBindings.iterator(); it.hasNext(); ) { + RecordedBinding b = it.next(); + if (condition.test(b)) { + it.remove(); + result.add(b); + } + } + } + return result; + } + + public Map getRecordedQueues() { + return recordedQueues; + } + + public Map getRecordedExchanges() { + return recordedExchanges; + } + + public List getRecordedBindings() { + return recordedBindings; + } + + public Map getRecordedConsumers() { + return consumers; + } + + @Override + public String toString() { + return this.delegate.toString(); + } + + /** Public API - {@inheritDoc} */ + @Override + public String getId() { + return this.delegate.getId(); + } + + /** Public API - {@inheritDoc} */ + @Override + public void setId(String id) { + this.delegate.setId(id); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/BackoffPolicy.java b/src/main/java/com/rabbitmq/client/impl/recovery/BackoffPolicy.java new file mode 100644 index 0000000000..036bf43b63 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/BackoffPolicy.java @@ -0,0 +1,34 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +/** + * Backoff policy for topology recovery retry attempts. + * + * @see DefaultRetryHandler + * @see TopologyRecoveryRetryHandlerBuilder + * @since 5.4.0 + */ +@FunctionalInterface +public interface BackoffPolicy { + + /** + * Wait depending on the current attempt number (1, 2, 3, etc) + * @param attemptNumber current attempt number + * @throws InterruptedException + */ + void backoff(int attemptNumber) throws InterruptedException; +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/ConsumerRecoveryListener.java b/src/main/java/com/rabbitmq/client/impl/recovery/ConsumerRecoveryListener.java new file mode 100644 index 0000000000..09cb4c7b40 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/ConsumerRecoveryListener.java @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +/** + * Not part of the public API. Mean to be used by JVM RabbitMQ clients that build on + * top of the Java client and need to be notified when consumer tag changes + * after recovery. + */ +public interface ConsumerRecoveryListener { + void consumerRecovered(String oldConsumerTag, String newConsumerTag); +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/DefaultRetryHandler.java b/src/main/java/com/rabbitmq/client/impl/recovery/DefaultRetryHandler.java new file mode 100644 index 0000000000..2e834c075d --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/DefaultRetryHandler.java @@ -0,0 +1,144 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; +import java.util.function.BiPredicate; + +/** + * Composable topology recovery retry handler. + * This retry handler implementations let the user choose the condition + * to trigger retry and the retry operation for each type of recoverable + * entities. The number of attempts and the backoff policy (time to wait + * between retries) are also configurable. + *

+ * See also {@link TopologyRecoveryRetryHandlerBuilder} to easily create + * instances and {@link TopologyRecoveryRetryLogic} for ready-to-use + * conditions and operations. + * + * @see TopologyRecoveryRetryHandlerBuilder + * @see TopologyRecoveryRetryLogic + * @since 5.4.0 + */ +public class DefaultRetryHandler implements RetryHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRetryHandler.class); + + protected final BiPredicate queueRecoveryRetryCondition; + protected final BiPredicate exchangeRecoveryRetryCondition; + protected final BiPredicate bindingRecoveryRetryCondition; + protected final BiPredicate consumerRecoveryRetryCondition; + + protected final RetryOperation queueRecoveryRetryOperation; + protected final RetryOperation exchangeRecoveryRetryOperation; + protected final RetryOperation bindingRecoveryRetryOperation; + protected final RetryOperation consumerRecoveryRetryOperation; + + protected final int retryAttempts; + + protected final BackoffPolicy backoffPolicy; + + public DefaultRetryHandler(BiPredicate queueRecoveryRetryCondition, + BiPredicate exchangeRecoveryRetryCondition, + BiPredicate bindingRecoveryRetryCondition, + BiPredicate consumerRecoveryRetryCondition, + RetryOperation queueRecoveryRetryOperation, + RetryOperation exchangeRecoveryRetryOperation, + RetryOperation bindingRecoveryRetryOperation, + RetryOperation consumerRecoveryRetryOperation, int retryAttempts, BackoffPolicy backoffPolicy) { + this.queueRecoveryRetryCondition = queueRecoveryRetryCondition; + this.exchangeRecoveryRetryCondition = exchangeRecoveryRetryCondition; + this.bindingRecoveryRetryCondition = bindingRecoveryRetryCondition; + this.consumerRecoveryRetryCondition = consumerRecoveryRetryCondition; + this.queueRecoveryRetryOperation = queueRecoveryRetryOperation; + this.exchangeRecoveryRetryOperation = exchangeRecoveryRetryOperation; + this.bindingRecoveryRetryOperation = bindingRecoveryRetryOperation; + this.consumerRecoveryRetryOperation = consumerRecoveryRetryOperation; + this.backoffPolicy = backoffPolicy; + if (retryAttempts <= 0) { + throw new IllegalArgumentException("Number of retry attempts must be greater than 0"); + } + this.retryAttempts = retryAttempts; + } + + @SuppressWarnings("unchecked") + @Override + public RetryResult retryQueueRecovery(RetryContext context) throws Exception { + return doRetry((BiPredicate) queueRecoveryRetryCondition, queueRecoveryRetryOperation, context.queue(), context); + } + + @SuppressWarnings("unchecked") + @Override + public RetryResult retryExchangeRecovery(RetryContext context) throws Exception { + return doRetry((BiPredicate) exchangeRecoveryRetryCondition, exchangeRecoveryRetryOperation, context.exchange(), context); + } + + @SuppressWarnings("unchecked") + @Override + public RetryResult retryBindingRecovery(RetryContext context) throws Exception { + return doRetry((BiPredicate) bindingRecoveryRetryCondition, bindingRecoveryRetryOperation, context.binding(), context); + } + + @SuppressWarnings("unchecked") + @Override + public RetryResult retryConsumerRecovery(RetryContext context) throws Exception { + return doRetry((BiPredicate) consumerRecoveryRetryCondition, consumerRecoveryRetryOperation, context.consumer(), context); + } + + protected RetryResult doRetry(BiPredicate condition, RetryOperation operation, RecordedEntity entity, RetryContext context) + throws Exception { + int attempts = 0; + Exception exception = context.exception(); + while (attempts < retryAttempts) { + if (condition.test(entity, exception)) { + log(entity, exception, attempts); + backoffPolicy.backoff(attempts + 1); + try { + Object result = operation.call(context); + return new RetryResult( + entity, result == null ? null : result.toString() + ); + } catch (Exception e) { + exception = e; + attempts++; + } + } else { + throw exception; + } + } + throw exception; + } + + protected void log(RecordedEntity entity, Exception exception, int attempts) { + LOGGER.info("Error while recovering {}, retrying with {} more attempt(s).", entity, retryAttempts - attempts, exception); + } + + public interface RetryOperation { + + T call(RetryContext context) throws Exception; + + default RetryOperation andThen(RetryOperation after) { + Objects.requireNonNull(after); + return (context) -> { + call(context); + return after.call(context); + }; + } + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/QueueRecoveryListener.java b/src/main/java/com/rabbitmq/client/impl/recovery/QueueRecoveryListener.java new file mode 100644 index 0000000000..32f2e616b4 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/QueueRecoveryListener.java @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +/** + * Not part of the public API. Mean to be used by JVM RabbitMQ clients that build on + * top of the Java client and need to be notified when server-name queue name changes + * after recovery. + */ +public interface QueueRecoveryListener { + void queueRecovered(String oldName, String newName); +} diff --git a/src/com/rabbitmq/client/impl/recovery/RecordedBinding.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedBinding.java similarity index 69% rename from src/com/rabbitmq/client/impl/recovery/RecordedBinding.java rename to src/main/java/com/rabbitmq/client/impl/recovery/RecordedBinding.java index 9a57d7a035..3a0bdad447 100644 --- a/src/com/rabbitmq/client/impl/recovery/RecordedBinding.java +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedBinding.java @@ -1,3 +1,18 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.impl.recovery; import java.io.IOException; @@ -44,6 +59,10 @@ public String getDestination() { return destination; } + public String getRoutingKey() { + return routingKey; + } + public Map getArguments() { return arguments; } @@ -52,9 +71,7 @@ public void setDestination(String destination) { this.destination = destination; } - public void recover() throws IOException { - // Implemented by subclasses - } + public abstract void recover() throws IOException; @Override public boolean equals(Object o) { diff --git a/src/com/rabbitmq/client/impl/recovery/RecordedConsumer.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedConsumer.java similarity index 53% rename from src/com/rabbitmq/client/impl/recovery/RecordedConsumer.java rename to src/main/java/com/rabbitmq/client/impl/recovery/RecordedConsumer.java index 16978e44b8..09ea88f2fd 100644 --- a/src/com/rabbitmq/client/impl/recovery/RecordedConsumer.java +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedConsumer.java @@ -1,3 +1,18 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.impl.recovery; import com.rabbitmq.client.Consumer; @@ -42,7 +57,7 @@ public RecordedConsumer autoAck(boolean value) { } public String recover() throws IOException { - this.consumerTag = this.channel.basicConsume(this.queue, this.autoAck, this.consumerTag, false, this.exclusive, this.arguments, this.consumer); + this.consumerTag = this.channel.getDelegate().basicConsume(this.queue, this.autoAck, this.consumerTag, false, this.exclusive, this.arguments, this.consumer); return this.consumerTag; } @@ -62,4 +77,9 @@ public void setQueue(String queue) { public String getConsumerTag() { return consumerTag; } + + @Override + public String toString() { + return "RecordedConsumer[tag=" + consumerTag + ", queue=" + queue + ", autoAck=" + autoAck + ", exclusive=" + exclusive + ", arguments=" + arguments + ", consumer=" + consumer + ", channel=" + channel + "]"; + } } diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RecordedEntity.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedEntity.java new file mode 100644 index 0000000000..5076caa7b3 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedEntity.java @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import com.rabbitmq.client.Channel; + +/** + * @since 3.3.0 + */ +public abstract class RecordedEntity { + protected final AutorecoveringChannel channel; + + public RecordedEntity(AutorecoveringChannel channel) { + this.channel = channel; + } + + public AutorecoveringChannel getChannel() { + return channel; + } + + public Channel getDelegateChannel() { + return channel.getDelegate(); + } +} diff --git a/src/com/rabbitmq/client/impl/recovery/RecordedExchange.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedExchange.java similarity index 51% rename from src/com/rabbitmq/client/impl/recovery/RecordedExchange.java rename to src/main/java/com/rabbitmq/client/impl/recovery/RecordedExchange.java index 5bc725c32e..603134bd4a 100644 --- a/src/com/rabbitmq/client/impl/recovery/RecordedExchange.java +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedExchange.java @@ -1,3 +1,18 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.impl.recovery; import java.io.IOException; @@ -16,6 +31,7 @@ public RecordedExchange(AutorecoveringChannel channel, String name) { super(channel, name); } + @Override public void recover() throws IOException { this.channel.getDelegate().exchangeDeclare(this.name, this.type, this.durable, this.autoDelete, this.arguments); } @@ -43,4 +59,9 @@ public RecordedExchange arguments(Map value) { public boolean isAutoDelete() { return autoDelete; } + + @Override + public String toString() { + return "RecordedExchange[name=" + name + ", type=" + type + ", durable=" + durable + ", autoDelete=" + autoDelete + ", arguments=" + arguments + ", channel=" + channel + "]"; + } } diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RecordedExchangeBinding.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedExchangeBinding.java new file mode 100644 index 0000000000..9fc48a8b84 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedExchangeBinding.java @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import java.io.IOException; + +/** + * @since 3.3.0 + */ +public class RecordedExchangeBinding extends RecordedBinding { + public RecordedExchangeBinding(AutorecoveringChannel channel) { + super(channel); + } + + @Override + public void recover() throws IOException { + this.channel.getDelegate().exchangeBind(this.destination, this.source, this.routingKey, this.arguments); + } + + @Override + public String toString() { + return "RecordedExchangeBinding[source=" + source + ", destination=" + destination + ", routingKey=" + routingKey + ", arguments=" + arguments + ", channel=" + channel + "]"; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RecordedNamedEntity.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedNamedEntity.java new file mode 100644 index 0000000000..3871d60b6f --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedNamedEntity.java @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import java.io.IOException; +/** + * @since 3.3.0 + */ +public abstract class RecordedNamedEntity extends RecordedEntity { + protected String name; + + public RecordedNamedEntity(AutorecoveringChannel channel, String name) { + super(channel); + this.name = name; + } + + public abstract void recover() throws IOException; + + public String getName() { + return name; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RecordedQueue.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedQueue.java new file mode 100644 index 0000000000..632430ce76 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedQueue.java @@ -0,0 +1,107 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import java.io.IOException; +import java.util.Map; + +/** + * @since 3.3.0 + */ +public class RecordedQueue extends RecordedNamedEntity { + public static final String EMPTY_STRING = ""; + + static final RecoveredQueueNameSupplier DEFAULT_QUEUE_NAME_SUPPLIER = q -> q.isServerNamed() ? EMPTY_STRING : q.name; + + private RecoveredQueueNameSupplier recoveredQueueNameSupplier = DEFAULT_QUEUE_NAME_SUPPLIER; + private boolean durable; + private boolean autoDelete; + private Map arguments; + private boolean exclusive; + private boolean serverNamed; + + public RecordedQueue(AutorecoveringChannel channel, String name) { + super(channel, name); + } + + public RecordedQueue exclusive(boolean value) { + this.exclusive = value; + return this; + } + + public boolean isExclusive() { + return this.exclusive; + } + + public RecordedQueue serverNamed(boolean value) { + this.serverNamed = value; + return this; + } + + public boolean isServerNamed() { + return this.serverNamed; + } + + @Override + public void recover() throws IOException { + this.name = this.channel.getDelegate().queueDeclare(this.getNameToUseForRecovery(), + this.durable, + this.exclusive, + this.autoDelete, + this.arguments).getQueue(); + } + + public String getNameToUseForRecovery() { + return recoveredQueueNameSupplier.getNameToUseForRecovery(this); + } + + public RecordedQueue durable(boolean value) { + this.durable = value; + return this; + } + + public boolean isDurable() { + return this.durable; + } + + public RecordedQueue autoDelete(boolean value) { + this.autoDelete = value; + return this; + } + + public boolean isAutoDelete() { + return this.autoDelete; + } + + public RecordedQueue arguments(Map value) { + this.arguments = value; + return this; + } + + public Map getArguments() { + return arguments; + } + + public RecordedQueue recoveredQueueNameSupplier(RecoveredQueueNameSupplier recoveredQueueNameSupplier) { + this.recoveredQueueNameSupplier = recoveredQueueNameSupplier; + return this; + } + + @Override + public String toString() { + return "RecordedQueue[name=" + name + ", durable=" + durable + ", autoDelete=" + autoDelete + ", exclusive=" + exclusive + ", arguments=" + arguments + "serverNamed=" + serverNamed + ", channel=" + channel + "]"; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RecordedQueueBinding.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedQueueBinding.java new file mode 100644 index 0000000000..a032aca7a9 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecordedQueueBinding.java @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import java.io.IOException; + +/** + * @since 3.3.0 + */ +public class RecordedQueueBinding extends RecordedBinding { + public RecordedQueueBinding(AutorecoveringChannel channel) { + super(channel); + } + + @Override + public void recover() throws IOException { + this.channel.getDelegate().queueBind(this.destination, this.source, this.routingKey, this.arguments); + } + + @Override + public String toString() { + return "RecordedQueueBinding[source=" + source + ", destination=" + destination + ", routingKey=" + routingKey + ", arguments=" + arguments + ", channel=" + channel + "]"; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RecoveredQueueNameSupplier.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveredQueueNameSupplier.java new file mode 100644 index 0000000000..c1fb3bd930 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveredQueueNameSupplier.java @@ -0,0 +1,18 @@ +package com.rabbitmq.client.impl.recovery; + +/** + * Functional callback interface that can be used to rename a queue during topology recovery. + * Can use along with {@link QueueRecoveryListener} to know when such a queue has been recovered successfully. + * + * @see QueueRecoveryListener + */ +@FunctionalInterface +public interface RecoveredQueueNameSupplier { + + /** + * Get the queue name to use when recovering this RecordedQueue entity + * @param recordedQueue the queue to be recovered + * @return new queue name + */ + String getNameToUseForRecovery(final RecordedQueue recordedQueue); +} \ No newline at end of file diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnection.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnection.java new file mode 100644 index 0000000000..9a1aa0ec11 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnection.java @@ -0,0 +1,49 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import com.rabbitmq.client.MetricsCollector; +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.ConnectionParams; +import com.rabbitmq.client.impl.FrameHandler; +import com.rabbitmq.client.observation.ObservationCollector; + +import java.util.concurrent.ThreadFactory; + +/** + * {@link com.rabbitmq.client.impl.AMQConnection} modification that uses {@link com.rabbitmq.client.impl.recovery.RecoveryAwareChannelN} + * @since 3.3.0 + */ +public class RecoveryAwareAMQConnection extends AMQConnection { + + public RecoveryAwareAMQConnection(ConnectionParams params, FrameHandler handler, + MetricsCollector metricsCollector, ObservationCollector observationCollector) { + super(params, handler, metricsCollector, observationCollector); + } + + public RecoveryAwareAMQConnection(ConnectionParams params, FrameHandler handler) { + super(params, handler); + } + + @Override + protected RecoveryAwareChannelManager instantiateChannelManager(int channelMax, ThreadFactory threadFactory) { + RecoveryAwareChannelManager recoveryAwareChannelManager = new RecoveryAwareChannelManager( + super._workService, channelMax, threadFactory, + this.metricsCollector, this.observationCollector); + configureChannelManager(recoveryAwareChannelManager); + return recoveryAwareChannelManager; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnectionFactory.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnectionFactory.java new file mode 100644 index 0000000000..b4754a2176 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareAMQConnectionFactory.java @@ -0,0 +1,105 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.ConnectionParams; +import com.rabbitmq.client.impl.FrameHandler; +import com.rabbitmq.client.impl.FrameHandlerFactory; +import com.rabbitmq.client.observation.ObservationCollector; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +public class RecoveryAwareAMQConnectionFactory { + private final ConnectionParams params; + private final FrameHandlerFactory factory; + private final AddressResolver addressResolver; + private final MetricsCollector metricsCollector; + private final ObservationCollector observationCollector; + + public RecoveryAwareAMQConnectionFactory(ConnectionParams params, FrameHandlerFactory factory, List

addrs) { + this(params, factory, new ListAddressResolver(addrs), new NoOpMetricsCollector(), + ObservationCollector.NO_OP); + } + + public RecoveryAwareAMQConnectionFactory(ConnectionParams params, FrameHandlerFactory factory, AddressResolver addressResolver) { + this(params, factory, addressResolver, new NoOpMetricsCollector(), + ObservationCollector.NO_OP); + } + + public RecoveryAwareAMQConnectionFactory(ConnectionParams params, FrameHandlerFactory factory, AddressResolver addressResolver, + MetricsCollector metricsCollector, ObservationCollector observationCollector) { + this.params = params; + this.factory = factory; + this.addressResolver = addressResolver; + this.metricsCollector = metricsCollector; + this.observationCollector = observationCollector; + } + + /** + * @return an interface to the connection + * @throws java.io.IOException if it encounters a problem + */ + // package protected API, made public for testing only + public RecoveryAwareAMQConnection newConnection() throws IOException, TimeoutException { + Exception lastException = null; + List
resolved = addressResolver.getAddresses(); + List
shuffled = addressResolver.maybeShuffle(resolved); + + for (Address addr : shuffled) { + try { + FrameHandler frameHandler = factory.create(addr, connectionName()); + RecoveryAwareAMQConnection conn = createConnection(params, frameHandler, metricsCollector); + conn.start(); + metricsCollector.newConnection(conn); + return conn; + } catch (IOException e) { + lastException = e; + } catch (TimeoutException te) { + lastException = te; + } + } + + if (lastException != null) { + if (lastException instanceof IOException) { + throw (IOException) lastException; + } else if (lastException instanceof TimeoutException) { + throw (TimeoutException) lastException; + } + } + throw new IOException("failed to connect"); + } + + protected RecoveryAwareAMQConnection createConnection(ConnectionParams params, FrameHandler handler, MetricsCollector metricsCollector) { + return new RecoveryAwareAMQConnection(params, handler, metricsCollector, + this.observationCollector); + } + + private String connectionName() { + Map clientProperties = params.getClientProperties(); + if (clientProperties == null) { + return null; + } else { + Object connectionName = clientProperties.get("connection_name"); + return connectionName == null ? null : connectionName.toString(); + } + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelManager.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelManager.java new file mode 100644 index 0000000000..e7bac96251 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelManager.java @@ -0,0 +1,52 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import com.rabbitmq.client.NoOpMetricsCollector; +import com.rabbitmq.client.MetricsCollector; +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.ChannelManager; +import com.rabbitmq.client.impl.ChannelN; +import com.rabbitmq.client.impl.ConsumerWorkService; +import com.rabbitmq.client.observation.ObservationCollector; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +/** + * @since 3.3.0 + */ +public class RecoveryAwareChannelManager extends ChannelManager { + public RecoveryAwareChannelManager(ConsumerWorkService workService, int channelMax) { + this(workService, channelMax, Executors.defaultThreadFactory()); + } + + public RecoveryAwareChannelManager(ConsumerWorkService workService, int channelMax, ThreadFactory threadFactory) { + super(workService, channelMax, threadFactory, new NoOpMetricsCollector(), ObservationCollector.NO_OP); + } + + public RecoveryAwareChannelManager(ConsumerWorkService workService, int channelMax, + ThreadFactory threadFactory, MetricsCollector metricsCollector, + ObservationCollector observationCollector) { + super(workService, channelMax, threadFactory, metricsCollector, observationCollector); + } + + @Override + protected ChannelN instantiateChannel(AMQConnection connection, int channelNumber, ConsumerWorkService workService) { + return new RecoveryAwareChannelN(connection, channelNumber, workService, + this.metricsCollector, this.observationCollector); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelN.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelN.java new file mode 100644 index 0000000000..66242c3979 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryAwareChannelN.java @@ -0,0 +1,156 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import com.rabbitmq.client.Command; +import com.rabbitmq.client.NoOpMetricsCollector; +import com.rabbitmq.client.MetricsCollector; +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.AMQImpl; +import com.rabbitmq.client.impl.ChannelN; +import com.rabbitmq.client.impl.ConsumerWorkService; +import com.rabbitmq.client.impl.AMQImpl.Basic; +import com.rabbitmq.client.observation.ObservationCollector; + +import java.io.IOException; + +/** + * {@link com.rabbitmq.client.impl.ChannelN} modification that keeps track of delivery + * tags and avoids sending
basic.ack
,
basic.nack
, and
basic.reject
+ * for stale tags. + * + * Consider a long running task a consumer has to perform. Say, it takes 15 minutes to complete. In the + * 15 minute window there is a reasonable chance of connection failure and recovery events. All delivery tags + * for the deliveries being processed won't be valid after recovery because they are "reset" for + * newly opened channels. This channel implementation will avoid sending out acknowledgements for such + * stale delivery tags and avoid a guaranteed channel-level exception (and thus channel closure). + * + * This is a sufficient solution in practice because all unacknowledged deliveries will be requeued + * by RabbitMQ automatically when it detects client connection loss. + * + * @since 3.3.0 + */ +public class RecoveryAwareChannelN extends ChannelN { + private volatile long maxSeenDeliveryTag = 0; + private volatile long activeDeliveryTagOffset = 0; + + /** + * Construct a new channel on the given connection with the given + * channel number. Usually not called directly - call + * Connection.createChannel instead. + * + * @param connection The connection associated with this channel + * @param channelNumber The channel number to be associated with this channel + * @param workService service for managing this channel's consumer callbacks + */ + public RecoveryAwareChannelN(AMQConnection connection, int channelNumber, ConsumerWorkService workService) { + this(connection, channelNumber, workService, new NoOpMetricsCollector(), + ObservationCollector.NO_OP); + } + + /** + * Construct a new channel on the given connection with the given + * channel number. Usually not called directly - call + * Connection.createChannel instead. + * + * @param connection The connection associated with this channel + * @param channelNumber The channel number to be associated with this channel + * @param workService service for managing this channel's consumer callbacks + * @param metricsCollector service for managing metrics + */ + public RecoveryAwareChannelN(AMQConnection connection, int channelNumber, ConsumerWorkService workService, + MetricsCollector metricsCollector, ObservationCollector observationCollector) { + super(connection, channelNumber, workService, + metricsCollector, observationCollector); + } + + @Override + protected void processDelivery(Command command, AMQImpl.Basic.Deliver method) { + long tag = method.getDeliveryTag(); + if(tag > maxSeenDeliveryTag) { + maxSeenDeliveryTag = tag; + } + super.processDelivery(command, offsetDeliveryTag(method)); + } + + private AMQImpl.Basic.Deliver offsetDeliveryTag(AMQImpl.Basic.Deliver method) { + return new AMQImpl.Basic.Deliver(method.getConsumerTag(), + method.getDeliveryTag() + activeDeliveryTagOffset, + method.getRedelivered(), + method.getExchange(), + method.getRoutingKey()); + } + + @Override + public void basicAck(long deliveryTag, boolean multiple) throws IOException { + long realTag = deliveryTag - activeDeliveryTagOffset; + // Last delivery is likely the same one a long running consumer is still processing, + // so realTag might end up being 0. + // has a special meaning in the protocol ("acknowledge all unacknowledged tags), + // so if the user explicitly asks for that with multiple = true, do it. + if(multiple && deliveryTag == 0) { + // 0 tag means ack all when multiple is set + realTag = 0; + } else if(realTag <= 0) { + // delivery tags start at 1, so the real tag is stale + // therefore we should do nothing + return; + } + transmit(new Basic.Ack(realTag, multiple)); + metricsCollector.basicAck(this, deliveryTag, multiple); + } + + @Override + public void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException { + // See the comment in basicAck above. + long realTag = deliveryTag - activeDeliveryTagOffset; + if(multiple && deliveryTag == 0) { + // 0 tag means nack all when multiple is set + realTag = 0; + } else if(realTag <= 0) { + // delivery tags start at 1, so the real tag is stale + // therefore we should do nothing + return; + } + transmit(new Basic.Nack(realTag, multiple, requeue)); + metricsCollector.basicNack(this, deliveryTag, requeue); + } + + @Override + public void basicReject(long deliveryTag, boolean requeue) throws IOException { + // note that the basicAck comment above does not apply + // here since basic.reject doesn't support rejecting + // multiple deliveries at once + long realTag = deliveryTag - activeDeliveryTagOffset; + if (realTag > 0) { + transmit(new Basic.Reject(realTag, requeue)); + metricsCollector.basicReject(this, deliveryTag, requeue); + } + } + + void inheritOffsetFrom(RecoveryAwareChannelN other) { + activeDeliveryTagOffset = other.getActiveDeliveryTagOffset() + other.getMaxSeenDeliveryTag(); + maxSeenDeliveryTag = 0; + } + + public long getMaxSeenDeliveryTag() { + return maxSeenDeliveryTag; + } + + public long getActiveDeliveryTagOffset() { + return activeDeliveryTagOffset; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryCanBeginListener.java b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryCanBeginListener.java new file mode 100644 index 0000000000..a5e517b97d --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RecoveryCanBeginListener.java @@ -0,0 +1,29 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import com.rabbitmq.client.ShutdownSignalException; + +/** + * Used internally to indicate when connection recovery can + * begin. + * This is package-local by design. + * + * @see Issue 135 on GitHub + */ +public interface RecoveryCanBeginListener { + void recoveryCanBegin(ShutdownSignalException cause); +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RetryContext.java b/src/main/java/com/rabbitmq/client/impl/recovery/RetryContext.java new file mode 100644 index 0000000000..f9b10a840f --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RetryContext.java @@ -0,0 +1,99 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +/** + * The context of a topology recovery retry operation. + * + * @since 5.4.0 + */ +public class RetryContext { + + private final RecordedEntity entity; + + private final Exception exception; + + private final AutorecoveringConnection connection; + + public RetryContext(RecordedEntity entity, Exception exception, AutorecoveringConnection connection) { + this.entity = entity; + this.exception = exception; + this.connection = connection; + } + + /** + * The underlying connection. + * + * @return + */ + public AutorecoveringConnection connection() { + return connection; + } + + /** + * The exception that triggered the retry attempt. + * + * @return + */ + public Exception exception() { + return exception; + } + + /** + * The to-be-recovered entity. + * + * @return + */ + public RecordedEntity entity() { + return entity; + } + + /** + * The to-be-recovered entity as a queue. + * + * @return + */ + public RecordedQueue queue() { + return (RecordedQueue) entity; + } + + /** + * The to-be-recovered entity as an exchange. + * + * @return + */ + public RecordedExchange exchange() { + return (RecordedExchange) entity; + } + + /** + * The to-be-recovered entity as a binding. + * + * @return + */ + public RecordedBinding binding() { + return (RecordedBinding) entity; + } + + /** + * The to-be-recovered entity as a consumer. + * + * @return + */ + public RecordedConsumer consumer() { + return (RecordedConsumer) entity; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RetryHandler.java b/src/main/java/com/rabbitmq/client/impl/recovery/RetryHandler.java new file mode 100644 index 0000000000..d840b88a04 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RetryHandler.java @@ -0,0 +1,62 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +/** + * Contract to retry failed operations during topology recovery. + * Not all operations have to be retried, it's a decision of the + * underlying implementation. + * + * @since 5.4.0 + */ +public interface RetryHandler { + + /** + * Retry a failed queue recovery operation. + * + * @param context the context of the retry + * @return the result of the retry attempt + * @throws Exception if the retry fails + */ + RetryResult retryQueueRecovery(RetryContext context) throws Exception; + + /** + * Retry a failed exchange recovery operation. + * + * @param context the context of the retry + * @return the result of the retry attempt + * @throws Exception if the retry fails + */ + RetryResult retryExchangeRecovery(RetryContext context) throws Exception; + + /** + * Retry a failed binding recovery operation. + * + * @param context the context of the retry + * @return the result of the retry attempt + * @throws Exception if the retry fails + */ + RetryResult retryBindingRecovery(RetryContext context) throws Exception; + + /** + * Retry a failed consumer recovery operation. + * + * @param context the context of the retry + * @return the result of the retry attempt + * @throws Exception if the retry fails + */ + RetryResult retryConsumerRecovery(RetryContext context) throws Exception; +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/RetryResult.java b/src/main/java/com/rabbitmq/client/impl/recovery/RetryResult.java new file mode 100644 index 0000000000..491a34fcd8 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/RetryResult.java @@ -0,0 +1,57 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +/** + * The retry of a retriable topology recovery operation. + * + * @since 5.4.0 + */ +public class RetryResult { + + /** + * The entity to recover. + */ + private final RecordedEntity recordedEntity; + + /** + * The result of the recovery operation. + * E.g. a consumer tag when recovering a consumer. + */ + private final Object result; + + public RetryResult(RecordedEntity recordedEntity, Object result) { + this.recordedEntity = recordedEntity; + this.result = result; + } + + /** + * The entity to recover. + * + * @return + */ + public RecordedEntity getRecordedEntity() { + return recordedEntity; + } + + /** + * The result of the recovery operation. + * E.g. a consumer tag when recovering a consumer. + */ + public Object getResult() { + return result; + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/TopologyRecoveryFilter.java b/src/main/java/com/rabbitmq/client/impl/recovery/TopologyRecoveryFilter.java new file mode 100644 index 0000000000..02364510a1 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/TopologyRecoveryFilter.java @@ -0,0 +1,60 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +/** + * Filter to know whether entities should be recovered or not. + * @since 4.8.0 + */ +public interface TopologyRecoveryFilter { + + /** + * Decides whether an exchange is recovered or not. + * @param recordedExchange + * @return true to recover the exchange, false otherwise + */ + default boolean filterExchange(RecordedExchange recordedExchange) { + return true; + } + + /** + * Decides whether a queue is recovered or not. + * @param recordedQueue + * @return true to recover the queue, false otherwise + */ + default boolean filterQueue(RecordedQueue recordedQueue) { + return true; + } + + /** + * Decides whether a binding is recovered or not. + * @param recordedBinding + * @return true to recover the binding, false otherwise + */ + default boolean filterBinding(RecordedBinding recordedBinding) { + return true; + } + + /** + * Decides whether a consumer is recovered or not. + * @param recordedConsumer + * @return true to recover the consumer, false otherwise + */ + default boolean filterConsumer(RecordedConsumer recordedConsumer) { + return true; + } + +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/TopologyRecoveryRetryHandlerBuilder.java b/src/main/java/com/rabbitmq/client/impl/recovery/TopologyRecoveryRetryHandlerBuilder.java new file mode 100644 index 0000000000..170620547e --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/TopologyRecoveryRetryHandlerBuilder.java @@ -0,0 +1,115 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import java.util.function.BiPredicate; + +/** + * Builder to ease creation of {@link DefaultRetryHandler} instances. + *

+ * Just override what you need. By default, retry conditions don't trigger retry, + * retry operations are no-op, the number of retry attempts is 2, and the backoff + * policy doesn't wait at all. + * + * @see DefaultRetryHandler + * @see TopologyRecoveryRetryLogic + * @since 5.4.0 + */ +public class TopologyRecoveryRetryHandlerBuilder { + + protected BiPredicate queueRecoveryRetryCondition = (q, e) -> false; + protected BiPredicate exchangeRecoveryRetryCondition = (ex, e) -> false; + protected BiPredicate bindingRecoveryRetryCondition = (b, e) -> false; + protected BiPredicate consumerRecoveryRetryCondition = (c, e) -> false; + + protected DefaultRetryHandler.RetryOperation queueRecoveryRetryOperation = context -> null; + protected DefaultRetryHandler.RetryOperation exchangeRecoveryRetryOperation = context -> null; + protected DefaultRetryHandler.RetryOperation bindingRecoveryRetryOperation = context -> null; + protected DefaultRetryHandler.RetryOperation consumerRecoveryRetryOperation = context -> null; + + protected int retryAttempts = 2; + + protected BackoffPolicy backoffPolicy = nbAttempts -> { + }; + + public static TopologyRecoveryRetryHandlerBuilder builder() { + return new TopologyRecoveryRetryHandlerBuilder(); + } + + public TopologyRecoveryRetryHandlerBuilder queueRecoveryRetryCondition( + BiPredicate queueRecoveryRetryCondition) { + this.queueRecoveryRetryCondition = queueRecoveryRetryCondition; + return this; + } + + public TopologyRecoveryRetryHandlerBuilder exchangeRecoveryRetryCondition( + BiPredicate exchangeRecoveryRetryCondition) { + this.exchangeRecoveryRetryCondition = exchangeRecoveryRetryCondition; + return this; + } + + public TopologyRecoveryRetryHandlerBuilder bindingRecoveryRetryCondition( + BiPredicate bindingRecoveryRetryCondition) { + this.bindingRecoveryRetryCondition = bindingRecoveryRetryCondition; + return this; + } + + public TopologyRecoveryRetryHandlerBuilder consumerRecoveryRetryCondition( + BiPredicate consumerRecoveryRetryCondition) { + this.consumerRecoveryRetryCondition = consumerRecoveryRetryCondition; + return this; + } + + public TopologyRecoveryRetryHandlerBuilder queueRecoveryRetryOperation(DefaultRetryHandler.RetryOperation queueRecoveryRetryOperation) { + this.queueRecoveryRetryOperation = queueRecoveryRetryOperation; + return this; + } + + public TopologyRecoveryRetryHandlerBuilder exchangeRecoveryRetryOperation(DefaultRetryHandler.RetryOperation exchangeRecoveryRetryOperation) { + this.exchangeRecoveryRetryOperation = exchangeRecoveryRetryOperation; + return this; + } + + public TopologyRecoveryRetryHandlerBuilder bindingRecoveryRetryOperation(DefaultRetryHandler.RetryOperation bindingRecoveryRetryOperation) { + this.bindingRecoveryRetryOperation = bindingRecoveryRetryOperation; + return this; + } + + public TopologyRecoveryRetryHandlerBuilder consumerRecoveryRetryOperation(DefaultRetryHandler.RetryOperation consumerRecoveryRetryOperation) { + this.consumerRecoveryRetryOperation = consumerRecoveryRetryOperation; + return this; + } + + public TopologyRecoveryRetryHandlerBuilder backoffPolicy(BackoffPolicy backoffPolicy) { + this.backoffPolicy = backoffPolicy; + return this; + } + + public TopologyRecoveryRetryHandlerBuilder retryAttempts(int retryAttempts) { + this.retryAttempts = retryAttempts; + return this; + } + + public RetryHandler build() { + return new DefaultRetryHandler( + queueRecoveryRetryCondition, exchangeRecoveryRetryCondition, + bindingRecoveryRetryCondition, consumerRecoveryRetryCondition, + queueRecoveryRetryOperation, exchangeRecoveryRetryOperation, + bindingRecoveryRetryOperation, consumerRecoveryRetryOperation, + retryAttempts, + backoffPolicy); + } +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/TopologyRecoveryRetryLogic.java b/src/main/java/com/rabbitmq/client/impl/recovery/TopologyRecoveryRetryLogic.java new file mode 100644 index 0000000000..d17e2f9809 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/TopologyRecoveryRetryLogic.java @@ -0,0 +1,241 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.ShutdownSignalException; +import com.rabbitmq.utility.Utility; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.BiPredicate; +import static com.rabbitmq.client.impl.recovery.TopologyRecoveryRetryHandlerBuilder.builder; + +/** + * Useful ready-to-use conditions and operations for {@link DefaultRetryHandler}. + * They're composed and used with the {@link TopologyRecoveryRetryHandlerBuilder}. + * + * @see DefaultRetryHandler + * @see RetryHandler + * @see TopologyRecoveryRetryHandlerBuilder + * @since 5.4.0 + */ +public abstract class TopologyRecoveryRetryLogic { + + /** + * Channel has been closed because of a resource that doesn't exist. + */ + public static final BiPredicate CHANNEL_CLOSED_NOT_FOUND = (entity, ex) -> { + if (ex.getCause() instanceof ShutdownSignalException) { + ShutdownSignalException cause = (ShutdownSignalException) ex.getCause(); + if (cause.getReason() instanceof AMQP.Channel.Close) { + return ((AMQP.Channel.Close) cause.getReason()).getReplyCode() == 404; + } + } + return false; + }; + + /** + * Recover a channel. + */ + public static final DefaultRetryHandler.RetryOperation RECOVER_CHANNEL = context -> { + if (!context.entity().getChannel().isOpen()) { + context.connection().recoverChannel(context.entity().getChannel()); + } + return null; + }; + + /** + * Recover a queue + */ + public static final DefaultRetryHandler.RetryOperation RECOVER_QUEUE = context -> { + if (context.entity() instanceof RecordedQueue) { + final RecordedQueue recordedQueue = context.queue(); + AutorecoveringConnection connection = context.connection(); + connection.recoverQueue(recordedQueue.getName(), recordedQueue); + } + return null; + }; + + /** + * Recover the destination queue of a binding. + */ + public static final DefaultRetryHandler.RetryOperation RECOVER_BINDING_QUEUE = context -> { + if (context.entity() instanceof RecordedQueueBinding) { + RecordedBinding binding = context.binding(); + AutorecoveringConnection connection = context.connection(); + RecordedQueue recordedQueue = connection.getRecordedQueues().get(binding.getDestination()); + if (recordedQueue != null) { + connection.recoverQueue(recordedQueue.getName(), recordedQueue); + } + } + return null; + }; + + /** + * Recover a binding. + */ + public static final DefaultRetryHandler.RetryOperation RECOVER_BINDING = context -> { + context.binding().recover(); + return null; + }; + + /** + * Recover earlier bindings that share the same queue as this retry context + */ + public static final DefaultRetryHandler.RetryOperation RECOVER_PREVIOUS_QUEUE_BINDINGS = context -> { + if (context.entity() instanceof RecordedQueueBinding) { + // recover all bindings for the same queue that were recovered before this current binding + // need to do this incase some bindings had already been recovered successfully before the queue was deleted & this binding failed + String queue = context.binding().getDestination(); + for (RecordedBinding recordedBinding : Utility.copy(context.connection().getRecordedBindings())) { + if (recordedBinding == context.entity()) { + // we have gotten to the binding in this context. Since this is an ordered list we can now break + // as we know we have recovered all the earlier bindings that may have existed on this queue + break; + } else if (recordedBinding instanceof RecordedQueueBinding && queue.equals(recordedBinding.getDestination())) { + recordedBinding.recover(); + } + } + } + return null; + }; + + /** + * Recover the queue of a consumer. + */ + public static final DefaultRetryHandler.RetryOperation RECOVER_CONSUMER_QUEUE = context -> { + if (context.entity() instanceof RecordedConsumer) { + RecordedConsumer consumer = context.consumer(); + AutorecoveringConnection connection = context.connection(); + RecordedQueue recordedQueue = connection.getRecordedQueues().get(consumer.getQueue()); + if (recordedQueue != null) { + connection.recoverQueue(recordedQueue.getName(), recordedQueue); + } + } + return null; + }; + + /** + * Recover all the bindings of the queue of a consumer. + */ + public static final DefaultRetryHandler.RetryOperation RECOVER_CONSUMER_QUEUE_BINDINGS = context -> { + if (context.entity() instanceof RecordedConsumer) { + String queue = context.consumer().getQueue(); + for (RecordedBinding recordedBinding : Utility.copy(context.connection().getRecordedBindings())) { + if (recordedBinding instanceof RecordedQueueBinding && queue.equals(recordedBinding.getDestination())) { + recordedBinding.recover(); + } + } + } + return null; + }; + + /** + * Recover a consumer. + */ + public static final DefaultRetryHandler.RetryOperation RECOVER_CONSUMER = context -> context.consumer().recover(); + + /** + * Recover earlier consumers that share the same channel as this retry context + */ + public static final DefaultRetryHandler.RetryOperation RECOVER_PREVIOUS_CONSUMERS = context -> { + if (context.entity() instanceof RecordedConsumer) { + // recover all consumers for the same channel that were recovered before this current + // consumer. need to do this incase some consumers had already been recovered + // successfully on a different queue before this one failed + final AutorecoveringChannel channel = context.consumer().getChannel(); + for (RecordedConsumer consumer : Utility.copy(context.connection().getRecordedConsumers()).values()) { + if (consumer == context.entity()) { + break; + } else if (consumer.getChannel() == channel) { + final RetryContext retryContext = new RetryContext(consumer, context.exception(), context.connection()); + RECOVER_CONSUMER_QUEUE.call(retryContext); + context.connection().recoverConsumer(consumer.getConsumerTag(), consumer); + RECOVER_CONSUMER_QUEUE_BINDINGS.call(retryContext); + } + } + return context.consumer().getConsumerTag(); + } + return null; + }; + + /** + * Recover earlier auto-delete or exclusive queues that share the same channel as this retry context + */ + public static final DefaultRetryHandler.RetryOperation RECOVER_PREVIOUS_AUTO_DELETE_QUEUES = context -> { + if (context.entity() instanceof RecordedQueue) { + AutorecoveringConnection connection = context.connection(); + RecordedQueue queue = context.queue(); + // recover all queues for the same channel that had already been recovered successfully before this queue failed. + // If the previous ones were auto-delete or exclusive, they need recovered again + for (Map.Entry entry : Utility.copy(connection.getRecordedQueues()).entrySet()) { + if (entry.getValue() == queue) { + // we have gotten to the queue in this context. Since this is an ordered map we can now break + // as we know we have recovered all the earlier queues on this channel + break; + } else if (queue.getChannel() == entry.getValue().getChannel() + && (entry.getValue().isAutoDelete() || entry.getValue().isExclusive())) { + connection.recoverQueue(entry.getKey(), entry.getValue()); + } + } + } else if (context.entity() instanceof RecordedQueueBinding) { + AutorecoveringConnection connection = context.connection(); + Set queues = new LinkedHashSet<>(); + for (Map.Entry entry : Utility.copy(connection.getRecordedQueues()).entrySet()) { + if (context.entity().getChannel() == entry.getValue().getChannel() + && (entry.getValue().isAutoDelete() || entry.getValue().isExclusive())) { + connection.recoverQueue(entry.getKey(), entry.getValue()); + queues.add(entry.getValue().getName()); + } + } + for (final RecordedBinding binding : Utility.copy(connection.getRecordedBindings())) { + if (binding instanceof RecordedQueueBinding && queues.contains(binding.getDestination())) { + binding.recover(); + } + } + } + return null; + }; + + /** + * Pre-configured {@link TopologyRecoveryRetryHandlerBuilder} that retries recovery of bindings and consumers + * when their respective queue is not found. + * + * This retry handler can be useful for long recovery processes, whereby auto-delete queues + * can be deleted between queue recovery and binding/consumer recovery. + * + * Also useful to retry channel-closed 404 errors that may arise with auto-delete queues during a cluster cycle. + */ + public static final TopologyRecoveryRetryHandlerBuilder RETRY_ON_QUEUE_NOT_FOUND_RETRY_HANDLER = builder() + .queueRecoveryRetryCondition(CHANNEL_CLOSED_NOT_FOUND) + .bindingRecoveryRetryCondition(CHANNEL_CLOSED_NOT_FOUND) + .consumerRecoveryRetryCondition(CHANNEL_CLOSED_NOT_FOUND) + .queueRecoveryRetryOperation(RECOVER_CHANNEL + .andThen(RECOVER_QUEUE) + .andThen(RECOVER_PREVIOUS_AUTO_DELETE_QUEUES)) + .bindingRecoveryRetryOperation(RECOVER_CHANNEL + .andThen(RECOVER_BINDING_QUEUE) + .andThen(RECOVER_BINDING) + .andThen(RECOVER_PREVIOUS_QUEUE_BINDINGS) + .andThen(RECOVER_PREVIOUS_AUTO_DELETE_QUEUES)) + .consumerRecoveryRetryOperation(RECOVER_CHANNEL + .andThen(RECOVER_CONSUMER_QUEUE) + .andThen(RECOVER_CONSUMER) + .andThen(RECOVER_CONSUMER_QUEUE_BINDINGS) + .andThen(RECOVER_PREVIOUS_CONSUMERS)); +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/Utils.java b/src/main/java/com/rabbitmq/client/impl/recovery/Utils.java new file mode 100644 index 0000000000..c83f2ebfd4 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/Utils.java @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl.recovery; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +final class Utils { + + private Utils() {} + + @FunctionalInterface + interface IoTimeoutExceptionRunnable { + + void run() throws IOException, TimeoutException; + + } + +} diff --git a/src/main/java/com/rabbitmq/client/impl/recovery/package-info.java b/src/main/java/com/rabbitmq/client/impl/recovery/package-info.java new file mode 100644 index 0000000000..d1432bdcf6 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/impl/recovery/package-info.java @@ -0,0 +1,4 @@ +/** + * Implementation of connection and topology recovery. + */ +package com.rabbitmq.client.impl.recovery; \ No newline at end of file diff --git a/src/main/java/com/rabbitmq/client/observation/NoOpObservationCollector.java b/src/main/java/com/rabbitmq/client/observation/NoOpObservationCollector.java new file mode 100644 index 0000000000..c4ff710b22 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/NoOpObservationCollector.java @@ -0,0 +1,45 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Consumer; +import com.rabbitmq.client.GetResponse; +import java.io.IOException; + +final class NoOpObservationCollector implements ObservationCollector { + + @Override + public void publish( + PublishCall call, + AMQP.Basic.Publish publish, + AMQP.BasicProperties properties, + byte[] body, + ConnectionInfo connectionInfo) + throws IOException { + call.publish(properties); + } + + @Override + public Consumer basicConsume(String queue, String consumerTag, Consumer consumer) { + return consumer; + } + + @Override + public GetResponse basicGet(BasicGetCall call, String queue) { + return call.get(); + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/ObservationCollector.java b/src/main/java/com/rabbitmq/client/observation/ObservationCollector.java new file mode 100644 index 0000000000..e600f7119d --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/ObservationCollector.java @@ -0,0 +1,106 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Consumer; +import com.rabbitmq.client.GetResponse; +import java.io.IOException; + +/** + * API to instrument operations in the AMQP client. The supported operations are publishing, + * asynchronous delivery, and synchronous delivery (basic.get). + * + *

Implementations can gather information and send it to tracing backends. This allows e.g. + * following the processing steps of a given message through different systems. + * + *

This is considered an SPI and is susceptible to change at any time. + * + * @since 5.19.0 + * @see com.rabbitmq.client.ConnectionFactory#setObservationCollector( ObservationCollector) + * @see com.rabbitmq.client.observation.micrometer.MicrometerObservationCollectorBuilder + */ +public interface ObservationCollector { + + ObservationCollector NO_OP = new NoOpObservationCollector(); + + /** + * Decorate message publishing. + * + *

Implementations are expected to call {@link PublishCall#publish( PublishCall, + * AMQP.Basic.Publish, AMQP.BasicProperties, byte[], ConnectionInfo)} to make sure the message is + * actually sent. + * + * @param call + * @param publish + * @param properties + * @param body + * @param connectionInfo + * @throws IOException + */ + void publish( + PublishCall call, + AMQP.Basic.Publish publish, + AMQP.BasicProperties properties, + byte[] body, + ConnectionInfo connectionInfo) + throws IOException; + + /** + * Decorate consumer registration. + * + *

Implementations are expected to decorate the appropriate {@link Consumer} callbacks. The + * original {@link Consumer} behavior should not be changed though. + * + * @param queue + * @param consumerTag + * @param consumer + * @return + */ + Consumer basicConsume(String queue, String consumerTag, Consumer consumer); + + /** + * Decorate message polling with basic.get. + * + *

Implementations are expected to {@link BasicGetCall#basicGet( BasicGetCall, String)} and + * return the same result. + * + * @param call + * @param queue + * @return + */ + GetResponse basicGet(BasicGetCall call, String queue); + + /** Underlying publishing call. */ + interface PublishCall { + + void publish(AMQP.BasicProperties properties) throws IOException; + } + + /** Underlying basic.get call. */ + interface BasicGetCall { + + GetResponse get(); + } + + /** Connection information. */ + interface ConnectionInfo { + + String getPeerAddress(); + + int getPeerPort(); + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultDeliverObservationConvention.java b/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultDeliverObservationConvention.java new file mode 100644 index 0000000000..7f10f4b595 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultDeliverObservationConvention.java @@ -0,0 +1,68 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation.micrometer; + +import com.rabbitmq.client.observation.micrometer.RabbitMqObservationDocumentation.HighCardinalityTags; +import com.rabbitmq.client.observation.micrometer.RabbitMqObservationDocumentation.LowCardinalityTags; +import io.micrometer.common.KeyValues; +import io.micrometer.common.util.StringUtils; + +/** + * Default implementation of {@link DeliverObservationConvention}. + * + * @since 5.19.0 + * @see DeliverObservationConvention + */ +abstract class DefaultDeliverObservationConvention implements DeliverObservationConvention { + + private final String operation; + + public DefaultDeliverObservationConvention(String operation) { + this.operation = operation; + } + + @Override + public String getContextualName(DeliverContext context) { + return source(context.getQueue()) + " " + operation; + } + + private String exchange(String destination) { + return StringUtils.isNotBlank(destination) ? destination : "amq.default"; + } + + private String source(String destination) { + return StringUtils.isNotBlank(destination) ? destination : "(anonymous)"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(DeliverContext context) { + return KeyValues.of( + LowCardinalityTags.MESSAGING_OPERATION.withValue(this.operation), + LowCardinalityTags.MESSAGING_SYSTEM.withValue("rabbitmq"), + LowCardinalityTags.NET_PROTOCOL_NAME.withValue("amqp"), + LowCardinalityTags.NET_PROTOCOL_VERSION.withValue("0.9.1")); + } + + @Override + public KeyValues getHighCardinalityKeyValues(DeliverContext context) { + return KeyValues.of( + HighCardinalityTags.MESSAGING_ROUTING_KEY.withValue(context.getRoutingKey()), + HighCardinalityTags.MESSAGING_DESTINATION_NAME.withValue(exchange(context.getExchange())), + HighCardinalityTags.MESSAGING_SOURCE_NAME.withValue(context.getQueue()), + HighCardinalityTags.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES.withValue( + String.valueOf(context.getPayloadSizeBytes()))); + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultProcessObservationConvention.java b/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultProcessObservationConvention.java new file mode 100644 index 0000000000..d1052b8c80 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultProcessObservationConvention.java @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation.micrometer; + +public class DefaultProcessObservationConvention extends DefaultDeliverObservationConvention { + + public DefaultProcessObservationConvention(String operation) { + super(operation); + } + + @Override + public String getName() { + return "rabbitmq.process"; + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultPublishObservationConvention.java b/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultPublishObservationConvention.java new file mode 100644 index 0000000000..9c510fcbc6 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultPublishObservationConvention.java @@ -0,0 +1,75 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation.micrometer; + +import com.rabbitmq.client.observation.micrometer.RabbitMqObservationDocumentation.HighCardinalityTags; +import com.rabbitmq.client.observation.micrometer.RabbitMqObservationDocumentation.LowCardinalityTags; +import io.micrometer.common.KeyValues; +import io.micrometer.common.util.StringUtils; + +/** + * Default implementation of {@link PublishObservationConvention}. + * + * @since 5.19.0 + */ +public class DefaultPublishObservationConvention implements PublishObservationConvention { + + private final String name; + + public DefaultPublishObservationConvention() { + this("rabbitmq.publish"); + } + + public DefaultPublishObservationConvention(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getContextualName(PublishContext context) { + return exchange(context.getRoutingKey()) + " publish"; + } + + private String exchange(String destination) { + return StringUtils.isNotBlank(destination) ? destination : "amq.default"; + } + + @Override + public KeyValues getLowCardinalityKeyValues(PublishContext context) { + return KeyValues.of( + LowCardinalityTags.MESSAGING_OPERATION.withValue("publish"), + LowCardinalityTags.MESSAGING_SYSTEM.withValue("rabbitmq"), + LowCardinalityTags.NET_PROTOCOL_NAME.withValue("amqp"), + LowCardinalityTags.NET_PROTOCOL_VERSION.withValue("0.9.1")); + } + + @Override + public KeyValues getHighCardinalityKeyValues(PublishContext context) { + return KeyValues.of( + HighCardinalityTags.MESSAGING_ROUTING_KEY.withValue(context.getRoutingKey()), + HighCardinalityTags.MESSAGING_DESTINATION_NAME.withValue(exchange(context.getExchange())), + HighCardinalityTags.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES.withValue( + String.valueOf(context.getPayloadSizeBytes())), + HighCardinalityTags.NET_SOCK_PEER_ADDR.withValue( + context.getConnectionInfo().getPeerAddress()), + HighCardinalityTags.NET_SOCK_PEER_PORT.withValue( + String.valueOf(context.getConnectionInfo().getPeerPort()))); + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultReceiveObservationConvention.java b/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultReceiveObservationConvention.java new file mode 100644 index 0000000000..eed66eccf2 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/micrometer/DefaultReceiveObservationConvention.java @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation.micrometer; + +public class DefaultReceiveObservationConvention extends DefaultDeliverObservationConvention { + + public DefaultReceiveObservationConvention(String operation) { + super(operation); + } + + @Override + public String getName() { + return "rabbitmq.receive"; + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/micrometer/DeliverContext.java b/src/main/java/com/rabbitmq/client/observation/micrometer/DeliverContext.java new file mode 100644 index 0000000000..1a0a3ba309 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/micrometer/DeliverContext.java @@ -0,0 +1,70 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation.micrometer; + +import io.micrometer.observation.transport.ReceiverContext; +import java.util.Map; + +/** + * {@link io.micrometer.observation.Observation.Context} for use with RabbitMQ client {@link + * io.micrometer.observation.Observation} instrumentation. + * + * @since 5.19.0 + */ +public class DeliverContext extends ReceiverContext> { + + private final String exchange; + private final String routingKey; + private final int payloadSizeBytes; + private final String queue; + + DeliverContext( + String exchange, + String routingKey, + String queue, + Map headers, + int payloadSizeBytes) { + super( + (hdrs, key) -> { + Object result = hdrs.get(key); + if (result == null) { + return null; + } + return String.valueOf(result); + }); + this.exchange = exchange; + this.routingKey = routingKey; + this.payloadSizeBytes = payloadSizeBytes; + this.queue = queue; + setCarrier(headers); + } + + public String getExchange() { + return this.exchange; + } + + public String getRoutingKey() { + return this.routingKey; + } + + public int getPayloadSizeBytes() { + return this.payloadSizeBytes; + } + + public String getQueue() { + return queue; + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/micrometer/DeliverObservationConvention.java b/src/main/java/com/rabbitmq/client/observation/micrometer/DeliverObservationConvention.java new file mode 100644 index 0000000000..e7dd25a27a --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/micrometer/DeliverObservationConvention.java @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; + +/** + * {@link ObservationConvention} for RabbitMQ client instrumentation. + * + * @since 5.19.0 + */ +public interface DeliverObservationConvention extends ObservationConvention { + + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof DeliverContext; + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/micrometer/MicrometerObservationCollector.java b/src/main/java/com/rabbitmq/client/observation/micrometer/MicrometerObservationCollector.java new file mode 100644 index 0000000000..88037a7a50 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/micrometer/MicrometerObservationCollector.java @@ -0,0 +1,231 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation.micrometer; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.observation.ObservationCollector; +import io.micrometer.common.KeyValues; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +class MicrometerObservationCollector implements ObservationCollector { + + private final ObservationRegistry registry; + + private final PublishObservationConvention customPublishConvention, defaultPublishConvention; + private final DeliverObservationConvention customProcessConvention, defaultProcessConvention; + private final DeliverObservationConvention customReceiveConvention, defaultReceiveConvention; + private final boolean keepObservationOpenOnBasicGet; + + MicrometerObservationCollector( + ObservationRegistry registry, + PublishObservationConvention customPublishConvention, + PublishObservationConvention defaultPublishConvention, + DeliverObservationConvention customProcessConvention, + DeliverObservationConvention defaultProcessConvention, + DeliverObservationConvention customReceiveConvention, + DeliverObservationConvention defaultReceiveConvention, + boolean keepObservationOpenOnBasicGet) { + this.registry = registry; + this.customPublishConvention = customPublishConvention; + this.defaultPublishConvention = defaultPublishConvention; + this.customProcessConvention = customProcessConvention; + this.defaultProcessConvention = defaultProcessConvention; + this.customReceiveConvention = customReceiveConvention; + this.defaultReceiveConvention = defaultReceiveConvention; + this.keepObservationOpenOnBasicGet = keepObservationOpenOnBasicGet; + } + + @Override + public void publish( + PublishCall call, + AMQP.Basic.Publish publish, + AMQP.BasicProperties properties, + byte[] body, + ConnectionInfo connectionInfo) + throws IOException { + Map headers; + if (properties.getHeaders() == null) { + headers = new HashMap<>(); + } else { + headers = new HashMap<>(properties.getHeaders()); + } + PublishContext micrometerPublishContext = + new PublishContext( + publish.getExchange(), + publish.getRoutingKey(), + headers, + body == null ? 0 : body.length, + connectionInfo); + AMQP.BasicProperties.Builder builder = properties.builder(); + builder.headers(headers); + Observation observation = + RabbitMqObservationDocumentation.PUBLISH_OBSERVATION.observation( + this.customPublishConvention, + this.defaultPublishConvention, + () -> micrometerPublishContext, + registry); + observation.start(); + try { + call.publish(builder.build()); + } catch (IOException | AlreadyClosedException e) { + observation.error(e); + throw e; + } finally { + observation.stop(); + } + } + + @Override + public Consumer basicConsume(String queue, String consumerTag, Consumer consumer) { + return new ObservationConsumer( + queue, + consumer, + this.registry, + this.customProcessConvention, + this.defaultProcessConvention); + } + + @Override + public GetResponse basicGet(BasicGetCall call, String queue) { + Observation observation = + Observation.createNotStarted("rabbitmq.receive", registry) + .highCardinalityKeyValues( + KeyValues.of( + RabbitMqObservationDocumentation.LowCardinalityTags.MESSAGING_OPERATION + .withValue("receive"), + RabbitMqObservationDocumentation.LowCardinalityTags.MESSAGING_SYSTEM.withValue( + "rabbitmq"))) + .start(); + boolean stopped = false; + try { + GetResponse response = call.get(); + if (response != null) { + observation.stop(); + stopped = true; + Map headers; + if (response.getProps() == null || response.getProps().getHeaders() == null) { + headers = Collections.emptyMap(); + } else { + headers = response.getProps().getHeaders(); + } + DeliverContext context = + new DeliverContext( + response.getEnvelope().getExchange(), + response.getEnvelope().getRoutingKey(), + queue, + headers, + response.getBody() == null ? 0 : response.getBody().length); + Observation receiveObservation = + RabbitMqObservationDocumentation.RECEIVE_OBSERVATION.observation( + customReceiveConvention, defaultReceiveConvention, () -> context, registry); + receiveObservation.start(); + if (this.keepObservationOpenOnBasicGet) { + receiveObservation.openScope(); + } else { + receiveObservation.stop(); + } + } + return response; + } catch (RuntimeException e) { + observation.error(e); + throw e; + } finally { + if (!stopped) { + observation.stop(); + } + } + } + + private static class ObservationConsumer implements Consumer { + + private final String queue; + private final Consumer delegate; + + private final ObservationRegistry observationRegistry; + + private final DeliverObservationConvention customConsumeConvention, defaultConsumeConvention; + + private ObservationConsumer( + String queue, + Consumer delegate, + ObservationRegistry observationRegistry, + DeliverObservationConvention customConsumeConvention, + DeliverObservationConvention defaultConsumeConvention) { + this.queue = queue; + this.delegate = delegate; + this.observationRegistry = observationRegistry; + this.customConsumeConvention = customConsumeConvention; + this.defaultConsumeConvention = defaultConsumeConvention; + } + + @Override + public void handleConsumeOk(String consumerTag) { + delegate.handleConsumeOk(consumerTag); + } + + @Override + public void handleCancelOk(String consumerTag) { + delegate.handleCancelOk(consumerTag); + } + + @Override + public void handleCancel(String consumerTag) throws IOException { + delegate.handleCancel(consumerTag); + } + + @Override + public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { + delegate.handleShutdownSignal(consumerTag, sig); + } + + @Override + public void handleRecoverOk(String consumerTag) { + delegate.handleRecoverOk(consumerTag); + } + + @Override + public void handleDelivery( + String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) + throws IOException { + Map headers; + if (properties == null || properties.getHeaders() == null) { + headers = Collections.emptyMap(); + } else { + headers = properties.getHeaders(); + } + DeliverContext context = + new DeliverContext( + envelope.getExchange(), + envelope.getRoutingKey(), + queue, + headers, + body == null ? 0 : body.length); + Observation observation = + RabbitMqObservationDocumentation.PROCESS_OBSERVATION.observation( + customConsumeConvention, + defaultConsumeConvention, + () -> context, + observationRegistry); + observation.observeChecked( + () -> delegate.handleDelivery(consumerTag, envelope, properties, body)); + } + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/micrometer/MicrometerObservationCollectorBuilder.java b/src/main/java/com/rabbitmq/client/observation/micrometer/MicrometerObservationCollectorBuilder.java new file mode 100644 index 0000000000..902d7256f2 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/micrometer/MicrometerObservationCollectorBuilder.java @@ -0,0 +1,215 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation.micrometer; + +import com.rabbitmq.client.observation.ObservationCollector; +import io.micrometer.observation.ObservationConvention; +import io.micrometer.observation.ObservationRegistry; +import java.util.function.Supplier; + +/** + * Builder to configure and create Micrometer + * Observation implementation of {@link ObservationCollector}. + * + * @since 5.19.0 + */ +public class MicrometerObservationCollectorBuilder { + + private ObservationRegistry registry = ObservationRegistry.NOOP; + private PublishObservationConvention customPublishObservationConvention; + private PublishObservationConvention defaultPublishObservationConvention = + new DefaultPublishObservationConvention(); + private DeliverObservationConvention customProcessObservationConvention; + private DeliverObservationConvention defaultProcessObservationConvention = + new DefaultProcessObservationConvention("process"); + private DeliverObservationConvention customReceiveObservationConvention; + private DeliverObservationConvention defaultReceiveObservationConvention = + new DefaultReceiveObservationConvention("receive"); + private boolean keepObservationStartedOnBasicGet = false; + + /** + * Set the {@link ObservationRegistry} to use. + * + *

Default is {@link ObservationRegistry#NOOP}. + * + * @param registry the registry + * @return this builder instance + */ + public MicrometerObservationCollectorBuilder registry(ObservationRegistry registry) { + this.registry = registry; + return this; + } + + /** + * Custom convention for basic.publish. + * + *

If not null, it will override any pre-configured conventions. + * + *

Default is null. + * + * @param customPublishObservationConvention the convention + * @return this builder instance + * @see io.micrometer.observation.docs.ObservationDocumentation#observation(ObservationConvention, + * ObservationConvention, Supplier, ObservationRegistry) + */ + public MicrometerObservationCollectorBuilder customPublishObservationConvention( + PublishObservationConvention customPublishObservationConvention) { + this.customPublishObservationConvention = customPublishObservationConvention; + return this; + } + + /** + * Default convention for basic.publish. + * + *

It will be picked if there was neither custom convention nor a pre-configured one via {@link + * ObservationRegistry}. + * + *

Default is {@link DefaultPublishObservationConvention}. + * + * @param defaultPublishObservationConvention the convention + * @return this builder instance + * @see io.micrometer.observation.docs.ObservationDocumentation#observation(ObservationConvention, + * ObservationConvention, Supplier, ObservationRegistry) + */ + public MicrometerObservationCollectorBuilder defaultPublishObservationConvention( + PublishObservationConvention defaultPublishObservationConvention) { + this.defaultPublishObservationConvention = defaultPublishObservationConvention; + return this; + } + + /** + * Custom convention for basic.deliver. + * + *

If not null, it will override any pre-configured conventions. + * + *

Default is null. + * + * @param customProcessObservationConvention the convention + * @return this builder instance + * @see io.micrometer.observation.docs.ObservationDocumentation#observation(ObservationConvention, + * ObservationConvention, Supplier, ObservationRegistry) + */ + public MicrometerObservationCollectorBuilder customProcessObservationConvention( + DeliverObservationConvention customProcessObservationConvention) { + this.customProcessObservationConvention = customProcessObservationConvention; + return this; + } + + /** + * Default convention for basic.delivery. + * + *

It will be picked if there was neither custom convention nor a pre-configured one via {@link + * ObservationRegistry}. + * + *

Default is DefaultProcessObservationConvention("process"). + * + * @param defaultProcessObservationConvention the convention + * @return this builder instance + * @see io.micrometer.observation.docs.ObservationDocumentation#observation(ObservationConvention, + * ObservationConvention, Supplier, ObservationRegistry) + */ + public MicrometerObservationCollectorBuilder defaultProcessObservationConvention( + DeliverObservationConvention defaultProcessObservationConvention) { + this.defaultProcessObservationConvention = defaultProcessObservationConvention; + return this; + } + + /** + * Custom convention for basic.get. + * + *

If not null, it will override any pre-configured conventions. + * + *

Default is null. + * + * @param customReceiveObservationConvention the convention + * @return this builder instance + * @see io.micrometer.observation.docs.ObservationDocumentation#observation(ObservationConvention, + * ObservationConvention, Supplier, ObservationRegistry) + */ + public MicrometerObservationCollectorBuilder customReceiveObservationConvention( + DeliverObservationConvention customReceiveObservationConvention) { + this.customReceiveObservationConvention = customReceiveObservationConvention; + return this; + } + + /** + * Default convention for basic.get. + * + *

It will be picked if there was neither custom convention nor a pre-configured one via {@link + * ObservationRegistry}. + * + *

Default is DefaultReceiveObservationConvention("receive"). + * + * @param defaultReceiveObservationConvention the convention + * @return this builder instance + * @see io.micrometer.observation.docs.ObservationDocumentation#observation(ObservationConvention, + * ObservationConvention, Supplier, ObservationRegistry) + */ + public MicrometerObservationCollectorBuilder defaultReceiveObservationConvention( + DeliverObservationConvention defaultReceiveObservationConvention) { + this.defaultReceiveObservationConvention = defaultReceiveObservationConvention; + return this; + } + + /** + * Whether to keep the basic.get observation started or not. + * + *

The {@link MicrometerObservationCollector} starts and stops the observation immediately + * after the message reception. This way the observation can have all the context from the + * received message but has a very short duration. This is the default behavior. + * + *

By setting this flag to true the collector does not stop the observation and + * opens a scope. The processing of the message can then be included in the observation. + * + *

This is then the responsibility of the developer to retrieve the observation and stop it to + * avoid memory leaks. Here is an example: + * + *

+   * GetResponse response = channel.basicGet(queue, true);
+   * // process the message...
+   * // stop the observation
+   * Observation.Scope scope = observationRegistry.getCurrentObservationScope();
+   * scope.close();
+   * scope.getCurrentObservation().stop();
+ * + * Default is false, that is stopping the observation immediately. + * + * @param keepObservationStartedOnBasicGet whether to keep the observation started or not + * @return this builder instance + */ + public MicrometerObservationCollectorBuilder keepObservationStartedOnBasicGet( + boolean keepObservationStartedOnBasicGet) { + this.keepObservationStartedOnBasicGet = keepObservationStartedOnBasicGet; + return this; + } + + /** + * Create the Micrometer {@link ObservationCollector}. + * + * @return the Micrometer observation collector + */ + public ObservationCollector build() { + return new MicrometerObservationCollector( + this.registry, + this.customPublishObservationConvention, + this.defaultPublishObservationConvention, + this.customProcessObservationConvention, + this.defaultProcessObservationConvention, + this.customReceiveObservationConvention, + this.defaultReceiveObservationConvention, + keepObservationStartedOnBasicGet); + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/micrometer/PublishContext.java b/src/main/java/com/rabbitmq/client/observation/micrometer/PublishContext.java new file mode 100644 index 0000000000..0038bbbae2 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/micrometer/PublishContext.java @@ -0,0 +1,64 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation.micrometer; + +import com.rabbitmq.client.observation.ObservationCollector; +import io.micrometer.observation.transport.SenderContext; +import java.util.Map; + +/** + * {@link io.micrometer.observation.Observation.Context} for use with RabbitMQ client {@link + * io.micrometer.observation.Observation} instrumentation. + * + * @since 5.19.0 + */ +public class PublishContext extends SenderContext> { + + private final String exchange; + private final String routingKey; + private final int payloadSizeBytes; + private final ObservationCollector.ConnectionInfo connectionInfo; + + PublishContext( + String exchange, + String routingKey, + Map headers, + int payloadSizeBytes, + ObservationCollector.ConnectionInfo connectionInfo) { + super((hdrs, key, value) -> hdrs.put(key, value)); + this.exchange = exchange; + this.routingKey = routingKey; + this.payloadSizeBytes = payloadSizeBytes; + this.connectionInfo = connectionInfo; + setCarrier(headers); + } + + public String getExchange() { + return this.exchange; + } + + public String getRoutingKey() { + return this.routingKey; + } + + public int getPayloadSizeBytes() { + return this.payloadSizeBytes; + } + + public ObservationCollector.ConnectionInfo getConnectionInfo() { + return this.connectionInfo; + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/micrometer/PublishObservationConvention.java b/src/main/java/com/rabbitmq/client/observation/micrometer/PublishObservationConvention.java new file mode 100644 index 0000000000..618a595948 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/micrometer/PublishObservationConvention.java @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation.micrometer; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; + +/** + * {@link ObservationConvention} for RabbitMQ client instrumentation. + * + * @since 5.19.0 + */ +public interface PublishObservationConvention extends ObservationConvention { + + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof PublishContext; + } +} diff --git a/src/main/java/com/rabbitmq/client/observation/micrometer/RabbitMqObservationDocumentation.java b/src/main/java/com/rabbitmq/client/observation/micrometer/RabbitMqObservationDocumentation.java new file mode 100644 index 0000000000..3ca70184f9 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/observation/micrometer/RabbitMqObservationDocumentation.java @@ -0,0 +1,166 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.observation.micrometer; + +import io.micrometer.common.docs.KeyName; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import io.micrometer.observation.docs.ObservationDocumentation; + +/** + * {@link ObservationDocumentation} for RabbitMQ Clients. + * + * @since 5.19.0 + */ +public enum RabbitMqObservationDocumentation implements ObservationDocumentation { + /** Observation for publishing a message. */ + PUBLISH_OBSERVATION { + + @Override + public Class> + getDefaultConvention() { + return DefaultPublishObservationConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return LowCardinalityTags.values(); + } + }, + + /** Observation for processing a message. */ + PROCESS_OBSERVATION { + + @Override + public Class> + getDefaultConvention() { + return DefaultProcessObservationConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return LowCardinalityTags.values(); + } + }, + + /** Observation for polling for a message with basic.get. */ + RECEIVE_OBSERVATION { + + @Override + public Class> + getDefaultConvention() { + return DefaultReceiveObservationConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return LowCardinalityTags.values(); + } + }; + + /** Low cardinality tags. */ + public enum LowCardinalityTags implements KeyName { + + /** A string identifying the messaging system. */ + MESSAGING_SYSTEM { + + @Override + public String asString() { + return "messaging.system"; + } + }, + + /** A string identifying the kind of messaging operation. */ + MESSAGING_OPERATION { + + @Override + public String asString() { + return "messaging.operation"; + } + }, + + /** A string identifying the protocol (AMQP). */ + NET_PROTOCOL_NAME { + + @Override + public String asString() { + return "net.protocol.name"; + } + }, + + /** A string identifying the protocol version (0.9.1). */ + NET_PROTOCOL_VERSION { + + @Override + public String asString() { + return "net.protocol.version"; + } + }, + } + + /** High cardinality tags. */ + public enum HighCardinalityTags implements KeyName { + + /** The message destination name. */ + MESSAGING_DESTINATION_NAME { + + @Override + public String asString() { + return "messaging.destination.name"; + } + }, + + /** RabbitMQ message routing key. */ + MESSAGING_ROUTING_KEY { + + @Override + public String asString() { + return "messaging.rabbitmq.destination.routing_key"; + } + }, + + /** The message destination name. */ + MESSAGING_SOURCE_NAME { + + @Override + public String asString() { + return "messaging.source.name"; + } + }, + + MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES { + + @Override + public String asString() { + return "messaging.message.payload_size_bytes"; + } + }, + + NET_SOCK_PEER_PORT { + @Override + public String asString() { + return "net.sock.peer.port"; + } + }, + + NET_SOCK_PEER_ADDR { + @Override + public String asString() { + return "net.sock.peer.addr"; + } + } + } +} diff --git a/src/main/java/com/rabbitmq/client/package-info.java b/src/main/java/com/rabbitmq/client/package-info.java new file mode 100644 index 0000000000..c231093c21 --- /dev/null +++ b/src/main/java/com/rabbitmq/client/package-info.java @@ -0,0 +1,5 @@ +/** + * The client API proper: classes and interfaces representing the AMQP + * connections, channels, and wire-protocol framing descriptors. + */ +package com.rabbitmq.client; \ No newline at end of file diff --git a/src/main/java/com/rabbitmq/tools/json/JSONUtil.java b/src/main/java/com/rabbitmq/tools/json/JSONUtil.java new file mode 100644 index 0000000000..b2549db23b --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/json/JSONUtil.java @@ -0,0 +1,100 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.tools.json; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Map; + +/** + * Utility methods for working with JSON objects in Java. + */ +public class JSONUtil { + + private static final Logger LOGGER = LoggerFactory.getLogger(JSONUtil.class); + + /** + * Uses reflection to fill public fields and Bean properties of + * the target object from the source Map. + */ + public static Object fill(Object target, Map source) + throws IntrospectionException, IllegalAccessException, InvocationTargetException { + return fill(target, source, true); + } + + /** + * Uses reflection to fill public fields and optionally Bean + * properties of the target object from the source Map. + */ + public static Object fill(Object target, Map source, boolean useProperties) + throws IntrospectionException, IllegalAccessException, InvocationTargetException { + if (useProperties) { + BeanInfo info = Introspector.getBeanInfo(target.getClass()); + + PropertyDescriptor[] props = info.getPropertyDescriptors(); + for (int i = 0; i < props.length; ++i) { + PropertyDescriptor prop = props[i]; + String name = prop.getName(); + Method setter = prop.getWriteMethod(); + if (setter != null && !Modifier.isStatic(setter.getModifiers())) { + setter.invoke(target, source.get(name)); + } + } + } + + Field[] ff = target.getClass().getDeclaredFields(); + for (int i = 0; i < ff.length; ++i) { + Field field = ff[i]; + int fieldMod = field.getModifiers(); + if (Modifier.isPublic(fieldMod) && !(Modifier.isFinal(fieldMod) || + Modifier.isStatic(fieldMod))) { + try { + field.set(target, source.get(field.getName())); + } catch (IllegalArgumentException iae) { + // no special error processing required + } + } + } + + return target; + } + + /** + * Ignores reflection exceptions while using reflection to fill + * public fields and Bean properties of the target object from the + * source Map. + */ + public static void tryFill(Object target, Map source) { + try { + fill(target, source); + } catch (IntrospectionException ie) { + LOGGER.error("Error in tryFill", ie); + } catch (IllegalAccessException iae) { + LOGGER.error("Error in tryFill", iae); + } catch (InvocationTargetException ite) { + LOGGER.error("Error in tryFill", ite); + } + } +} diff --git a/src/main/java/com/rabbitmq/tools/json/package-info.java b/src/main/java/com/rabbitmq/tools/json/package-info.java new file mode 100644 index 0000000000..0a7d76e65f --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/json/package-info.java @@ -0,0 +1,4 @@ +/** + * JSON reader/writer and utility classes. + */ +package com.rabbitmq.tools.json; \ No newline at end of file diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java new file mode 100644 index 0000000000..7eae103d7d --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JacksonJsonRpcMapper.java @@ -0,0 +1,204 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.tools.jsonrpc; + +import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.databind.MappingJsonFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ValueNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * {@link JsonRpcMapper} based on Jackson. + *

+ * Uses the streaming and databind modules. You need to add the appropriate dependency + * to the classpath if you want to use this class, as the RabbitMQ Java client + * library does not pull Jackson automatically when using a dependency management + * tool like Maven or Gradle. + *

+ * Make sure to use the latest version of the Jackson library, as the version used in the + * RabbitMQ Java client can be a little bit behind. + * + * @see JsonRpcMapper + * @since 5.4.0 + */ +public class JacksonJsonRpcMapper implements JsonRpcMapper { + + private static final Logger LOGGER = LoggerFactory.getLogger(JacksonJsonRpcMapper.class); + + private final ObjectMapper mapper; + + public JacksonJsonRpcMapper(ObjectMapper mapper) { + this.mapper = mapper; + } + + public JacksonJsonRpcMapper() { + this(new ObjectMapper()); + } + + @Override + public JsonRpcRequest parse(String requestBody, ServiceDescription description) { + JsonFactory jsonFactory = new MappingJsonFactory(); + String method = null, version = null; + final List parameters = new ArrayList<>(); + Object id = null; + try (JsonParser parser = jsonFactory.createParser(requestBody)) { + while (parser.nextToken() != null) { + JsonToken token = parser.currentToken(); + if (token == JsonToken.FIELD_NAME) { + String name = parser.currentName(); + token = parser.nextToken(); + if ("method".equals(name)) { + method = parser.getValueAsString(); + } else if ("id".equals(name)) { + TreeNode node = parser.readValueAsTree(); + if (node instanceof ValueNode) { + ValueNode idNode = (ValueNode) node; + if (idNode.isNull()) { + id = null; + } else if (idNode.isTextual()) { + id = idNode.asText(); + } else if (idNode.isNumber()) { + id = Long.valueOf(idNode.asLong()); + } else { + LOGGER.warn("ID type not null, text, or number {}, ignoring", idNode); + } + } else { + LOGGER.warn("ID not a scalar value {}, ignoring", node); + } + } else if ("version".equals(name)) { + version = parser.getValueAsString(); + } else if ("params".equals(name)) { + if (token == JsonToken.START_ARRAY) { + while (parser.nextToken() != JsonToken.END_ARRAY) { + parameters.add(parser.readValueAsTree()); + } + } else { + throw new IllegalStateException("Field params must be an array"); + } + } + } + } + } catch (IOException e) { + throw new JsonRpcMappingException("Error during JSON parsing", e); + } + + if (method == null) { + throw new IllegalArgumentException("Could not find method to invoke in request"); + } + + List convertedParameters = new ArrayList<>(parameters.size()); + if (!parameters.isEmpty()) { + ProcedureDescription proc = description.getProcedure(method, parameters.size()); + Method internalMethod = proc.internal_getMethod(); + for (int i = 0; i < internalMethod.getParameterCount(); i++) { + TreeNode parameterNode = parameters.get(i); + try { + Class parameterType = internalMethod.getParameterTypes()[i]; + Object value = convert(parameterNode, parameterType); + convertedParameters.add(value); + } catch (IOException e) { + throw new JsonRpcMappingException("Error during parameter conversion", e); + } + } + } + + return new JsonRpcRequest( + id, version, method, + convertedParameters.toArray() + ); + } + + @Override + @SuppressWarnings("unchecked") + public JsonRpcResponse parse(String responseBody, Class expectedReturnType) { + JsonFactory jsonFactory = new MappingJsonFactory(); + Object result = null; + JsonRpcException exception = null; + Map errorMap = null; + try (JsonParser parser = jsonFactory.createParser(responseBody)) { + while (parser.nextToken() != null) { + JsonToken token = parser.currentToken(); + if (token == JsonToken.FIELD_NAME) { + String name = parser.currentName(); + if ("result".equals(name)) { + parser.nextToken(); + if (expectedReturnType == Void.TYPE) { + result = null; + } else { + result = convert(parser.readValueAsTree(), expectedReturnType); + } + } else if ("error".equals(name)) { + errorMap = (Map) convert(parser.readValueAsTree(), Map.class); + exception = new JsonRpcException( + errorMap.toString(), + (String) errorMap.get("name"), + errorMap.get("code") == null ? 0 : (Integer) errorMap.get("code"), + (String) errorMap.get("message"), + errorMap + ); + } + } + } + } catch (IOException e) { + throw new JsonRpcMappingException("Error during JSON parsing", e); + } + return new JsonRpcResponse(result, errorMap, exception); + } + + @Override + public String write(Object input) { + try { + return mapper.writeValueAsString(input); + } catch (JsonProcessingException e) { + throw new JsonRpcMappingException("Error during JSON serialization", e); + } + } + + protected Object convert(TreeNode node, Class expectedType) throws IOException { + Object value; + if (expectedType.isPrimitive()) { + ValueNode valueNode = (ValueNode) node; + if (expectedType == Boolean.TYPE) { + value = valueNode.booleanValue(); + } else if (expectedType == Character.TYPE) { + value = valueNode.textValue().charAt(0); + } else if (expectedType == Short.TYPE) { + value = valueNode.shortValue(); + } else if (expectedType == Integer.TYPE) { + value = valueNode.intValue(); + } else if (expectedType == Long.TYPE) { + value = valueNode.longValue(); + } else if (expectedType == Float.TYPE) { + value = valueNode.floatValue(); + } else if (expectedType == Double.TYPE) { + value = valueNode.doubleValue(); + } else { + throw new IllegalArgumentException("Primitive type not supported: " + expectedType); + } + } else { + value = mapper.readValue(node.traverse(), expectedType); + } + return value; + } +} diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java new file mode 100644 index 0000000000..1cdaa131c0 --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcClient.java @@ -0,0 +1,221 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.tools.jsonrpc; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.RpcClient; +import com.rabbitmq.client.RpcClientParams; +import com.rabbitmq.client.ShutdownSignalException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +/** + * JSON-RPC is a lightweight + * RPC mechanism using JSON + * as a data language for request and reply messages. It is + * rapidly becoming a standard in web development, where it is + * used to make RPC requests over HTTP. RabbitMQ provides an + * AMQP transport binding for JSON-RPC in the form of the + * JsonRpcClient class. + *

+ * JSON-RPC services are self-describing - each service is able + * to list its supported procedures, and each procedure + * describes its parameters and types. An instance of + * JsonRpcClient retrieves its service description using the + * standard system.describe procedure when it is + * constructed, and uses the information to coerce parameter + * types appropriately. A JSON service description is parsed + * into instances of ServiceDescription. Client + * code can access the service description by reading the + * serviceDescription field of + * JsonRpcClient instances. + *

+ * {@link JsonRpcClient} delegates JSON parsing and generating to + * a {@link JsonRpcMapper}. + * + * @see #call(String, Object[]) + * @see JsonRpcMapper + * @see JacksonJsonRpcMapper + */ +public class JsonRpcClient extends RpcClient implements InvocationHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(JsonRpcClient.class); + private final JsonRpcMapper mapper; + /** + * Holds the JSON-RPC service description for this client. + */ + private ServiceDescription serviceDescription; + + /** + * Construct a new {@link JsonRpcClient}, passing the {@link RpcClientParams} through {@link RpcClient}'s constructor. + *

+ * The service description record is + * retrieved from the server during construction. + * + * @param rpcClientParams + * @param mapper + * @throws IOException + * @throws JsonRpcException + * @throws TimeoutException + */ + public JsonRpcClient(RpcClientParams rpcClientParams, JsonRpcMapper mapper) + throws IOException, JsonRpcException, TimeoutException { + super(rpcClientParams); + this.mapper = mapper; + retrieveServiceDescription(); + } + + /** + * Construct a new JsonRpcClient, passing the parameters through + * to RpcClient's constructor. The service description record is + * retrieved from the server during construction. + * + * @throws TimeoutException if a response is not received within the timeout specified, if any + */ + public JsonRpcClient(Channel channel, String exchange, String routingKey, int timeout, JsonRpcMapper mapper) + throws IOException, JsonRpcException, TimeoutException { + super(new RpcClientParams() + .channel(channel) + .exchange(exchange) + .routingKey(routingKey) + .timeout(timeout) + ); + this.mapper = mapper; + retrieveServiceDescription(); + } + + /** + * Construct a new JsonRpcClient, passing the parameters through + * to RpcClient's constructor. The service description record is + * retrieved from the server during construction. + * + * @throws TimeoutException if a response is not received within the timeout specified, if any + */ + public JsonRpcClient(Channel channel, String exchange, String routingKey, int timeout) + throws IOException, JsonRpcException, TimeoutException { + this(channel, exchange, routingKey, timeout, new JacksonJsonRpcMapper()); + } + + public JsonRpcClient(Channel channel, String exchange, String routingKey) + throws IOException, JsonRpcException, TimeoutException { + this(channel, exchange, routingKey, RpcClient.NO_TIMEOUT); + } + + /** + * Private API - parses a JSON-RPC reply object, checking it for exceptions. + * + * @return the result contained within the reply, if no exception is found + * Throws JsonRpcException if the reply object contained an exception + */ + private Object checkReply(JsonRpcMapper.JsonRpcResponse reply) + throws JsonRpcException { + if (reply.getError() != null) { + throw reply.getException(); + } + + return reply.getResult(); + } + + /** + * Public API - builds, encodes and sends a JSON-RPC request, and + * waits for the response. + * + * @return the result contained within the reply, if no exception is found + * @throws JsonRpcException if the reply object contained an exception + * @throws TimeoutException if a response is not received within the timeout specified, if any + */ + public Object call(String method, Object[] params) throws IOException, JsonRpcException, TimeoutException { + Map request = new HashMap(); + request.put("id", null); + request.put("method", method); + request.put("version", ServiceDescription.JSON_RPC_VERSION); + params = (params == null) ? new Object[0] : params; + request.put("params", params); + String requestStr = mapper.write(request); + try { + String replyStr = this.stringCall(requestStr); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Reply string: {}", replyStr); + } + Class expectedType; + if ("system.describe".equals(method) && params.length == 0) { + expectedType = Map.class; + } else { + ProcedureDescription proc = serviceDescription.getProcedure(method, params.length); + expectedType = proc.getReturnType(); + } + JsonRpcMapper.JsonRpcResponse reply = mapper.parse(replyStr, expectedType); + + return checkReply(reply); + } catch (ShutdownSignalException ex) { + throw new IOException(ex.getMessage()); // wrap, re-throw + } + } + + /** + * Public API - implements InvocationHandler.invoke. This is + * useful for constructing dynamic proxies for JSON-RPC + * interfaces. + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + return call(method.getName(), args); + } + + /** + * Public API - gets a dynamic proxy for a particular interface class. + */ + @SuppressWarnings("unchecked") + public T createProxy(Class klass) + throws IllegalArgumentException { + return (T) Proxy.newProxyInstance(klass.getClassLoader(), + new Class[] { klass }, + this); + } + + + + /** + * Public API - gets the service description record that this + * service loaded from the server itself at construction time. + */ + public ServiceDescription getServiceDescription() { + return serviceDescription; + } + + /** + * Private API - invokes the "system.describe" method on the + * server, and parses and stores the resulting service description + * in this object. + * TODO: Avoid calling this from the constructor. + * + * @throws TimeoutException if a response is not received within the timeout specified, if any + */ + private void retrieveServiceDescription() throws IOException, JsonRpcException, TimeoutException { + @SuppressWarnings("unchecked") + Map rawServiceDescription = (Map) call("system.describe", null); + serviceDescription = new ServiceDescription(rawServiceDescription); + } +} diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcException.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcException.java new file mode 100644 index 0000000000..67304c043a --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcException.java @@ -0,0 +1,75 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.tools.jsonrpc; + +/** + * Thrown when a JSON-RPC service indicates an error occurred during a call. + */ +public class JsonRpcException extends Exception { + + /** + * Default serialized version ID + */ + private static final long serialVersionUID = 1L; + /** + * Usually the constant string, "JSONRPCError" + */ + private final String name; + /** + * Error code + */ + private final int code; + /** + * Error message + */ + private final String message; + /** + * Error detail object - may not always be present or meaningful + */ + private final Object error; + + public JsonRpcException() { + this.name = null; + this.code = -1; + this.message = null; + this.error = null; + } + + public JsonRpcException(String detailMessage, String name, int code, String message, Object error) { + super(detailMessage); + this.name = name; + this.code = code; + this.message = message; + this.error = error; + } + + public String getName() { + return name; + } + + public int getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } + + public Object getError() { + return error; + } +} diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java new file mode 100644 index 0000000000..b8fc8061d4 --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMapper.java @@ -0,0 +1,115 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.tools.jsonrpc; + +/** + * Abstraction to handle JSON parsing and generation. + * Used by {@link JsonRpcServer} and {@link JsonRpcClient}. + * + * @since 5.4.0 + */ +public interface JsonRpcMapper { + + /** + * Parses a JSON RPC request. + * The {@link ServiceDescription} can be used + * to look up the invoked procedure and learn about + * its signature. + * @param requestBody + * @param description + * @return + */ + JsonRpcRequest parse(String requestBody, ServiceDescription description); + + /** + * Parses a JSON RPC response. + * @param responseBody + * @param expectedType + * @return + */ + JsonRpcResponse parse(String responseBody, Class expectedType); + + /** + * Serialize an object into JSON. + * @param input + * @return + */ + String write(Object input); + + class JsonRpcRequest { + + private final Object id; + private final String version; + private final String method; + private final Object[] parameters; + + public JsonRpcRequest(Object id, String version, String method, Object[] parameters) { + this.id = id; + this.version = version; + this.method = method; + this.parameters = parameters; + } + + public Object getId() { + return id; + } + + public String getVersion() { + return version; + } + + public String getMethod() { + return method; + } + + public Object[] getParameters() { + return parameters; + } + + public boolean isSystem() { + return method.startsWith("system."); + } + + public boolean isSystemDescribe() { + return "system.describe".equals(method); + } + } + + class JsonRpcResponse { + + private final Object result; + private final Object error; + private final JsonRpcException exception; + + public JsonRpcResponse(Object result, Object error, JsonRpcException exception) { + this.result = result; + this.error = error; + this.exception = exception; + } + + public Object getError() { + return error; + } + + public Object getResult() { + return result; + } + + public JsonRpcException getException() { + return exception; + } + } +} diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMappingException.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMappingException.java new file mode 100644 index 0000000000..6876e538f6 --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcMappingException.java @@ -0,0 +1,27 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.tools.jsonrpc; + +/** + * + * @since 5.4.0 + */ +public class JsonRpcMappingException extends RuntimeException { + + public JsonRpcMappingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java new file mode 100644 index 0000000000..37ce79e0f6 --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java @@ -0,0 +1,255 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.tools.jsonrpc; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.StringRpcServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * JSON-RPC Server class. + *

+ * Given a Java {@link Class}, representing an interface, and an + * implementation of that interface, JsonRpcServer will reflect on the + * class to construct the {@link ServiceDescription}, and will route + * incoming requests for methods on the interface to the + * implementation object while the mainloop() is running. + *

+ * {@link JsonRpcServer} delegates JSON parsing and generating to + * a {@link JsonRpcMapper}. + * + * @see com.rabbitmq.client.RpcServer + * @see JsonRpcClient + * @see JsonRpcMapper + * @see JacksonJsonRpcMapper + */ +public class JsonRpcServer extends StringRpcServer { + + private static final Logger LOGGER = LoggerFactory.getLogger(JsonRpcServer.class); + private final JsonRpcMapper mapper; + /** + * Holds the JSON-RPC service description for this client. + */ + private ServiceDescription serviceDescription; + /** + * The instance backing this server. + */ + private Object interfaceInstance; + + public JsonRpcServer(Channel channel, + Class interfaceClass, + Object interfaceInstance, JsonRpcMapper mapper) + throws IOException { + super(channel); + this.mapper = mapper; + init(interfaceClass, interfaceInstance); + } + + /** + * Construct a server that talks to the outside world using the + * given channel, and constructs a fresh temporary + * queue. Use getQueueName() to discover the created queue name. + * + * @param channel AMQP channel to use + * @param interfaceClass Java interface that this server is exposing to the world + * @param interfaceInstance Java instance (of interfaceClass) that is being exposed + * @throws IOException if something goes wrong during an AMQP operation + */ + public JsonRpcServer(Channel channel, + Class interfaceClass, + Object interfaceInstance) + throws IOException { + this(channel, interfaceClass, interfaceInstance, new JacksonJsonRpcMapper()); + } + + public JsonRpcServer(Channel channel, + String queueName, + Class interfaceClass, + Object interfaceInstance, JsonRpcMapper mapper) + throws IOException { + super(channel, queueName); + this.mapper = mapper; + init(interfaceClass, interfaceInstance); + } + + /** + * Construct a server that talks to the outside world using the + * given channel and queue name. Our superclass, + * RpcServer, expects the queue to exist at the time of + * construction. + * + * @param channel AMQP channel to use + * @param queueName AMQP queue name to listen for requests on + * @param interfaceClass Java interface that this server is exposing to the world + * @param interfaceInstance Java instance (of interfaceClass) that is being exposed + * @throws IOException if something goes wrong during an AMQP operation + */ + public JsonRpcServer(Channel channel, + String queueName, + Class interfaceClass, + Object interfaceInstance) + throws IOException { + this(channel, queueName, interfaceClass, interfaceInstance, new JacksonJsonRpcMapper()); + } + + private void init(Class interfaceClass, Object interfaceInstance) { + /** + * The interface this server implements. + */ + this.interfaceInstance = interfaceInstance; + this.serviceDescription = new ServiceDescription(interfaceClass); + } + + /** + * Override our superclass' method, dispatching to doCall. + */ + @Override + public String handleStringCall(String requestBody, AMQP.BasicProperties replyProperties) { + String replyBody = doCall(requestBody); + return replyBody; + } + + /** + * Runs a single JSON-RPC request. + * + * @param requestBody the JSON-RPC request string (a JSON encoded value) + * @return a JSON-RPC response string (a JSON encoded value) + */ + public String doCall(String requestBody) { + Object id; + String method; + Object[] params; + String response; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Request: {}", requestBody); + } + try { + JsonRpcMapper.JsonRpcRequest request = mapper.parse(requestBody, serviceDescription); + if (request == null) { + response = errorResponse(null, 400, "Bad Request", null); + } else if (!ServiceDescription.JSON_RPC_VERSION.equals(request.getVersion())) { + response = errorResponse(null, 505, "JSONRPC version not supported", null); + } else { + id = request.getId(); + method = request.getMethod(); + params = request.getParameters(); + if (request.isSystemDescribe()) { + response = resultResponse(id, serviceDescription); + } else if (request.isSystem()) { + response = errorResponse(id, 403, "System methods forbidden", null); + } else { + Object result; + try { + Method matchingMethod = matchingMethod(method, params); + if (LOGGER.isDebugEnabled()) { + Collection parametersValuesAndTypes = new ArrayList(); + if (params != null) { + for (Object param : params) { + parametersValuesAndTypes.add( + String.format("%s (%s)", param, param == null ? "?" : param.getClass()) + ); + } + } + LOGGER.debug("About to invoke {} method with parameters {}", matchingMethod, parametersValuesAndTypes); + } + result = matchingMethod.invoke(interfaceInstance, params); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Invocation returned {} ({})", result, result == null ? "?" : result.getClass()); + } + response = resultResponse(id, result); + } catch (Throwable t) { + LOGGER.info("Error while processing JSON RPC request", t); + response = errorResponse(id, 500, "Internal Server Error", t); + } + } + } + } catch (ClassCastException cce) { + // Bogus request! + response = errorResponse(null, 400, "Bad Request", null); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Response: {}", response); + } + + return response; + } + + /** + * Retrieves the best matching method for the given method name and parameters. + *

+ * Subclasses may override this if they have specialised + * dispatching requirements, so long as they continue to honour + * their ServiceDescription. + */ + public Method matchingMethod(String methodName, Object[] params) { + ProcedureDescription proc = serviceDescription.getProcedure(methodName, params.length); + return proc.internal_getMethod(); + } + + /** + * Construct and encode a JSON-RPC error response for the request + * ID given, using the code, message, and possible + * (JSON-encodable) argument passed in. + */ + private String errorResponse(Object id, int code, String message, Object errorArg) { + Map err = new HashMap(); + err.put("name", "JSONRPCError"); + err.put("code", code); + err.put("message", message); + err.put("error", errorArg); + return response(id, "error", err); + } + + /** + * Construct and encode a JSON-RPC success response for the + * request ID given, using the result value passed in. + */ + private String resultResponse(Object id, Object result) { + return response(id, "result", result); + } + + /** + * Private API - used by errorResponse and resultResponse. + */ + private String response(Object id, String label, Object value) { + Map resp = new HashMap(); + resp.put("version", ServiceDescription.JSON_RPC_VERSION); + if (id != null) { + resp.put("id", id); + } + resp.put(label, value); + String respStr = mapper.write(resp); + return respStr; + } + + /** + * Public API - gets the service description record that this + * service built from interfaceClass at construction time. + */ + public ServiceDescription getServiceDescription() { + return serviceDescription; + } +} diff --git a/src/com/rabbitmq/tools/jsonrpc/ParameterDescription.java b/src/main/java/com/rabbitmq/tools/jsonrpc/ParameterDescription.java similarity index 51% rename from src/com/rabbitmq/tools/jsonrpc/ParameterDescription.java rename to src/main/java/com/rabbitmq/tools/jsonrpc/ParameterDescription.java index 407a978691..d167671a14 100644 --- a/src/com/rabbitmq/tools/jsonrpc/ParameterDescription.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/ParameterDescription.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.tools.jsonrpc; @@ -27,12 +26,12 @@ */ public class ParameterDescription { /** The parameter name. */ - public String name; + private String name; /** * The parameter type - one of "bit", "num", "str", "arr", * "obj", "any" or "nil". */ - public String type; + private String type; public ParameterDescription() { // Nothing to do here. @@ -58,4 +57,20 @@ public static String lookup(Class c) { if (Collection.class.isAssignableFrom(c)) return "arr"; return "any"; } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public void setName(String name) { + this.name = name; + } + + public void setType(String type) { + this.type = type; + } } diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/ProcedureDescription.java b/src/main/java/com/rabbitmq/tools/jsonrpc/ProcedureDescription.java new file mode 100644 index 0000000000..b94adb23b4 --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/ProcedureDescription.java @@ -0,0 +1,174 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.tools.jsonrpc; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +import com.rabbitmq.tools.json.JSONUtil; + +/** + * Description of a single JSON-RPC procedure. + */ +public class ProcedureDescription { + /** Procedure name */ + private String name; + /** Human-readable procedure summary */ + private String summary; + /** Human-readable instructions for how to get information on the procedure's operation */ + private String help; + /** True if this procedure is idempotent, that is, can be accessed via HTTP GET */ + private boolean idempotent; + + /** Descriptions of parameters for this procedure */ + private ParameterDescription[] params; + /** Return type for this procedure */ + private String returnType; + private String javaReturnType; + private Class _javaReturnTypeAsClass; + + /** Reflected method object, used for service invocation */ + private Method method; + + public ProcedureDescription(Map pm) { + JSONUtil.tryFill(this, pm); + + @SuppressWarnings("unchecked") + List> p = (List>) pm.get("params"); + params = new ParameterDescription[p.size()]; + int count = 0; + for (Map param_map: p) { + ParameterDescription param = new ParameterDescription(param_map); + params[count++] = param; + } + } + + public ProcedureDescription(Method m) { + this.method = m; + this.name = m.getName(); + this.summary = ""; + this.help = ""; + this.idempotent = false; + Class[] parameterTypes = m.getParameterTypes(); + this.params = new ParameterDescription[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + params[i] = new ParameterDescription(i, parameterTypes[i]); + } + this.returnType = ParameterDescription.lookup(m.getReturnType()); + this.javaReturnType = m.getReturnType().getName(); + } + + public ProcedureDescription() { + // no work to do here + } + + /** Getter for return type */ + public String getReturn() { return returnType; } + /** Private API - used via reflection during parsing/loading */ + public void setReturn(String value) { returnType = value; } + + /** Private API - used to get the reflected method object, for servers */ + public Method internal_getMethod() { return method; } + + public String getJavaReturnType() { + return javaReturnType; + } + + public void setJavaReturnType(String javaReturnType) { + this.javaReturnType = javaReturnType; + this._javaReturnTypeAsClass = computeReturnTypeAsJavaClass(); + } + + public Class getReturnType() { + return _javaReturnTypeAsClass; + } + + private Class computeReturnTypeAsJavaClass() { + try { + if ("int".equals(javaReturnType)) { + return Integer.TYPE; + } else if ("double".equals(javaReturnType)) { + return Double.TYPE; + } else if ("long".equals(javaReturnType)) { + return Long.TYPE; + } else if ("boolean".equals(javaReturnType)) { + return Boolean.TYPE; + } else if ("char".equals(javaReturnType)) { + return Character.TYPE; + } else if ("byte".equals(javaReturnType)) { + return Byte.TYPE; + } else if ("short".equals(javaReturnType)) { + return Short.TYPE; + } else if ("float".equals(javaReturnType)) { + return Float.TYPE; + } else if ("void".equals(javaReturnType)) { + return Void.TYPE; + } else { + return Class.forName(javaReturnType); + } + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Unable to load class: " + javaReturnType, e); + } + } + + /** Gets an array of parameter descriptions for all this procedure's parameters */ + public ParameterDescription[] internal_getParams() { + return params; + } + + /** Retrieves the parameter count for this procedure */ + public int arity() { + return (params == null) ? 0 : params.length; + } + + public ParameterDescription[] getParams() { + return params; + } + + public String getName() { + return name; + } + + public String getSummary() { + return summary; + } + + public String getHelp() { + return help; + } + + public boolean isIdempotent() { + return idempotent; + } + + public void setName(String name) { + this.name = name; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public void setHelp(String help) { + this.help = help; + } + + public void setIdempotent(boolean idempotent) { + this.idempotent = idempotent; + } +} diff --git a/src/com/rabbitmq/tools/jsonrpc/ServiceDescription.java b/src/main/java/com/rabbitmq/tools/jsonrpc/ServiceDescription.java similarity index 56% rename from src/com/rabbitmq/tools/jsonrpc/ServiceDescription.java rename to src/main/java/com/rabbitmq/tools/jsonrpc/ServiceDescription.java index 332526d984..925efa73fc 100644 --- a/src/com/rabbitmq/tools/jsonrpc/ServiceDescription.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/ServiceDescription.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.tools.jsonrpc; @@ -31,15 +30,15 @@ public class ServiceDescription { public static final String JSON_RPC_VERSION = "1.1"; /** The service name */ - public String name; + private String name; /** ID for the service */ - public String id; + private String id; /** Version of the service */ - public String version; + private String version; /** Human-readable summary for the service */ - public String summary; + private String summary; /** Human-readable instructions for how to get information on the service's operation */ - public String help; + private String help; /** Map from procedure name to {@link ProcedureDescription} */ private Map procedures; @@ -49,7 +48,7 @@ public ServiceDescription(Map rawServiceDescription) { } public ServiceDescription(Class klass) { - this.procedures = new HashMap(); + this.procedures = new HashMap<>(); for (Method m: klass.getMethods()) { ProcedureDescription proc = new ProcedureDescription(m); addProcedure(proc); @@ -67,7 +66,7 @@ public Collection getProcs() { /** Private API - used via reflection during parsing/loading */ public void setProcs(Collection> p) { - procedures = new HashMap(); + procedures = new HashMap<>(); for (Map pm: p) { ProcedureDescription proc = new ProcedureDescription(pm); addProcedure(proc); @@ -76,7 +75,7 @@ public void setProcs(Collection> p) { /** Private API - used during initialization */ private void addProcedure(ProcedureDescription proc) { - procedures.put(proc.name + "/" + proc.arity(), proc); + procedures.put(proc.getName() + "/" + proc.arity(), proc); } /** @@ -92,4 +91,44 @@ public ProcedureDescription getProcedure(String newname, int arity) { } return proc; } + + public String getName() { + return name; + } + + public String getId() { + return id; + } + + public String getVersion() { + return version; + } + + public String getSummary() { + return summary; + } + + public String getHelp() { + return help; + } + + public void setName(String name) { + this.name = name; + } + + public void setId(String id) { + this.id = id; + } + + public void setVersion(String version) { + this.version = version; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public void setHelp(String help) { + this.help = help; + } } diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/package-info.java b/src/main/java/com/rabbitmq/tools/jsonrpc/package-info.java new file mode 100644 index 0000000000..4cc7826c55 --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/package-info.java @@ -0,0 +1,4 @@ +/** + * JSON-RPC client and server classes for supporting JSON-RPC over an AMQP transport. + */ +package com.rabbitmq.tools.jsonrpc; \ No newline at end of file diff --git a/src/main/java/com/rabbitmq/tools/package-info.java b/src/main/java/com/rabbitmq/tools/package-info.java new file mode 100644 index 0000000000..2b8be98550 --- /dev/null +++ b/src/main/java/com/rabbitmq/tools/package-info.java @@ -0,0 +1,4 @@ +/** + * Non-core utilities and administration tools. + */ +package com.rabbitmq.tools; \ No newline at end of file diff --git a/src/com/rabbitmq/utility/BlockingCell.java b/src/main/java/com/rabbitmq/utility/BlockingCell.java similarity index 60% rename from src/com/rabbitmq/utility/BlockingCell.java rename to src/main/java/com/rabbitmq/utility/BlockingCell.java index 74f545e81e..a78d3a88e6 100644 --- a/src/com/rabbitmq/utility/BlockingCell.java +++ b/src/main/java/com/rabbitmq/utility/BlockingCell.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.utility; @@ -29,7 +28,7 @@ public class BlockingCell { /** Will be null until a value is supplied, and possibly still then. */ private T _value; - private static final long NANOS_IN_MILLI = 1000 * 1000; + private static final long NANOS_IN_MILLI = 1000L * 1000L; private static final long INFINITY = -1; @@ -64,12 +63,13 @@ public synchronized T get() throws InterruptedException { public synchronized T get(long timeout) throws InterruptedException, TimeoutException { if (timeout == INFINITY) return get(); - if (timeout < 0) - throw new AssertionError("Timeout cannot be less than zero"); + if (timeout < 0) { + throw new IllegalArgumentException("Timeout cannot be less than zero"); + } - long maxTime = System.currentTimeMillis() + timeout; - long now; - while (!_filled && (now = System.currentTimeMillis()) < maxTime) { + long now = System.nanoTime() / NANOS_IN_MILLI; + long maxTime = now + timeout; + while (!_filled && (now = (System.nanoTime() / NANOS_IN_MILLI)) < maxTime) { wait(maxTime - now); } @@ -84,11 +84,19 @@ public synchronized T get(long timeout) throws InterruptedException, TimeoutExce * @return the waited-for value */ public synchronized T uninterruptibleGet() { - while (true) { - try { - return get(); - } catch (InterruptedException ex) { - // no special handling necessary + boolean wasInterrupted = false; + try { + while (true) { + try { + return get(); + } catch (InterruptedException ex) { + // no special handling necessary + wasInterrupted = true; + } + } + } finally { + if (wasInterrupted) { + Thread.currentThread().interrupt(); } } } @@ -105,25 +113,32 @@ public synchronized T uninterruptibleGet() { public synchronized T uninterruptibleGet(int timeout) throws TimeoutException { long now = System.nanoTime() / NANOS_IN_MILLI; long runTime = now + timeout; - - do { - try { - return get(runTime - now); - } catch (InterruptedException e) { - // Ignore. + boolean wasInterrupted = false; + try { + do { + try { + return get(runTime - now); + } catch (InterruptedException e) { + // Ignore. + wasInterrupted = true; + } + } while ((timeout == INFINITY) || ((now = System.nanoTime() / NANOS_IN_MILLI) < runTime)); + } finally { + if (wasInterrupted) { + Thread.currentThread().interrupt(); } - } while ((timeout == INFINITY) || ((now = System.nanoTime() / NANOS_IN_MILLI) < runTime)); + } throw new TimeoutException(); } /** - * Store a value in this BlockingCell, throwing AssertionError if the cell already has a value. + * Store a value in this BlockingCell, throwing {@link IllegalStateException} if the cell already has a value. * @param newValue the new value to store */ public synchronized void set(T newValue) { if (_filled) { - throw new AssertionError("BlockingCell can only be set once"); + throw new IllegalStateException("BlockingCell can only be set once"); } _value = newValue; _filled = true; diff --git a/src/main/java/com/rabbitmq/utility/BlockingValueOrException.java b/src/main/java/com/rabbitmq/utility/BlockingValueOrException.java new file mode 100644 index 0000000000..332ab3ddba --- /dev/null +++ b/src/main/java/com/rabbitmq/utility/BlockingValueOrException.java @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.utility; + +import java.util.concurrent.TimeoutException; + +public class BlockingValueOrException> + extends BlockingCell> +{ + public void setValue(V v) { + super.set(ValueOrException.makeValue(v)); + } + + public void setException(E e) { + super.set(ValueOrException.makeException(e)); + } + + public V uninterruptibleGetValue() throws E { + return uninterruptibleGet().getValue(); + } + + public V uninterruptibleGetValue(int timeout) throws E, TimeoutException { + return uninterruptibleGet(timeout).getValue(); + } +} diff --git a/src/com/rabbitmq/utility/IntAllocator.java b/src/main/java/com/rabbitmq/utility/IntAllocator.java similarity index 79% rename from src/com/rabbitmq/utility/IntAllocator.java rename to src/main/java/com/rabbitmq/utility/IntAllocator.java index 85a32130d8..1f8ed5efd5 100644 --- a/src/com/rabbitmq/utility/IntAllocator.java +++ b/src/main/java/com/rabbitmq/utility/IntAllocator.java @@ -1,35 +1,39 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.utility; import java.util.BitSet; /** - * A class for allocating integers from a given range that uses a + *

+ * A class for allocating integers from a given range that uses a * {@link BitSet} representation of the free integers. + *

* - *

Concurrent Semantics:
+ *

Concurrency Semantics:

* This class is not thread safe. * - *

Implementation notes: - *
This was originally an ordered chain of non-overlapping Intervals, + *

Implementation notes:

+ *

This was originally an ordered chain of non-overlapping Intervals, * together with a fixed size array cache for freed integers. - *
{@link #reserve(int)} was expensive in this scheme, whereas in the + *

+ *

+ * {@link #reserve(int)} was expensive in this scheme, whereas in the * present implementation it is O(1), as is {@link #free(int)}. + *

*

Although {@link #allocate()} is slightly slower than O(1) and in the * worst case could be O(N), the use of a "lastIndex" field * for starting the next scan for free integers means this is negligible. @@ -84,7 +88,7 @@ public int allocate() { /** * Make the provided integer available for allocation again. This operation * runs in O(1) time. - *
No error checking is performed, so if you double free or free an + * No error checking is performed, so if you double free or free an * integer that was not originally allocated the results are undefined. * @param reservation the previously allocated integer to free */ @@ -95,7 +99,6 @@ public void free(int reservation) { /** * Attempt to reserve the provided ID as if it had been allocated. Returns * true if it is available, false otherwise. - *
* This operation runs in O(1) time. * @param reservation the integer to be allocated, if possible * @return true if allocated, false diff --git a/src/main/java/com/rabbitmq/utility/SensibleClone.java b/src/main/java/com/rabbitmq/utility/SensibleClone.java new file mode 100644 index 0000000000..b33fe627b3 --- /dev/null +++ b/src/main/java/com/rabbitmq/utility/SensibleClone.java @@ -0,0 +1,31 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.utility; + +/** + * This interface exists as a workaround for the annoyingness of java.lang.Cloneable. + * It is used for generic methods which need to accept something they can actually clone + * (Object.clone is protected and java.lang.Cloneable does not define a public clone method) + * and want to provide some guarantees of the type of the cloned object. + */ +public interface SensibleClone> extends Cloneable { + + /** + * Like Object.clone but sensible; in particular, public and declared to return + * the right type. + */ + public T sensibleClone(); +} diff --git a/src/main/java/com/rabbitmq/utility/Utility.java b/src/main/java/com/rabbitmq/utility/Utility.java new file mode 100644 index 0000000000..597dcebf0b --- /dev/null +++ b/src/main/java/com/rabbitmq/utility/Utility.java @@ -0,0 +1,122 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.utility; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Catch-all holder class for static helper methods. + */ + +public class Utility { + static class ThrowableCreatedElsewhere extends Throwable { + /** Default for non-checking. */ + private static final long serialVersionUID = 1L; + + public ThrowableCreatedElsewhere(Throwable throwable) { + super(throwable.getClass() + " created elsewhere"); + this.setStackTrace(throwable.getStackTrace()); + } + + @Override + public synchronized Throwable fillInStackTrace(){ + return this; + } + } + + public static > T fixStackTrace(T throwable) { + throwable = throwable.sensibleClone(); + + if(throwable.getCause() == null) { + // We'd like to preserve the original stack trace in the cause. + // Unfortunately Java doesn't let you set the cause once it's been + // set once. This means we have to choose between either + // - not preserving the type + // - sometimes losing the original stack trace + // - performing nasty reflective voodoo which may or may not work + // We only lose the original stack trace when there's a root cause + // which will hopefully be enlightening enough on its own that it + // doesn't matter too much. + try { + throwable.initCause(new ThrowableCreatedElsewhere(throwable)); + } catch(IllegalStateException e) { + // This exception was explicitly initialised with a null cause. + // Alas this means we can't set the cause even though it has none. + // Thanks. + } + } + + + throwable.fillInStackTrace(); + // We want to remove fixStackTrace from the trace. + StackTraceElement[] existing = throwable.getStackTrace(); + StackTraceElement[] newTrace = new StackTraceElement[existing.length - 1]; + System.arraycopy(existing, 1, newTrace, 0, newTrace.length); + throwable.setStackTrace(newTrace); + return throwable; + } + + /** + * Synchronizes on the set and then returns a copy of the set that is safe to iterate over. Useful when wanting to do thread-safe iteration over + * a Set wrapped in {@link Collections#synchronizedSet(Set)}. + * + * @param set + * The set, which may not be {@code null} + * @return LinkedHashSet copy of the set + */ + public static Set copy(final Set set) { + // No Sonar: this very list instance can be synchronized in other places of its owning class + synchronized (set) { //NOSONAR + return new LinkedHashSet<>(set); + } + } + + /** + * Synchronizes on the list and then returns a copy of the list that is safe to iterate over. Useful when wanting to do thread-safe iteration over + * a List wrapped in {@link Collections#synchronizedList(List)}. + * + * @param list + * The list, which may not be {@code null} + * @return ArrayList copy of the list + */ + public static List copy(final List list) { + // No Sonar: this very list instance can be synchronized in other places of its owning class + synchronized (list) { //NOSONAR + return new ArrayList<>(list); + } + } + + /** + * Synchronizes on the map and then returns a copy of the map that is safe to iterate over. Useful when wanting to do thread-safe iteration over a + * Map wrapped in {@link Collections#synchronizedMap(Map)} + * + * @param map + * The map, which may not be {@code null} + * @return LinkedHashMap copy of the map + */ + public static Map copy(final Map map) { + // No Sonar: this very map instance can be synchronized in other places of its owning class + synchronized (map) { //NOSONAR + return new LinkedHashMap<>(map); + } + } +} diff --git a/src/com/rabbitmq/utility/ValueOrException.java b/src/main/java/com/rabbitmq/utility/ValueOrException.java similarity index 69% rename from src/com/rabbitmq/utility/ValueOrException.java rename to src/main/java/com/rabbitmq/utility/ValueOrException.java index 4a9e549448..8aff2c08ef 100644 --- a/src/com/rabbitmq/utility/ValueOrException.java +++ b/src/main/java/com/rabbitmq/utility/ValueOrException.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.utility; diff --git a/src/main/java/com/rabbitmq/utility/package-info.java b/src/main/java/com/rabbitmq/utility/package-info.java new file mode 100644 index 0000000000..9ae72725af --- /dev/null +++ b/src/main/java/com/rabbitmq/utility/package-info.java @@ -0,0 +1,4 @@ +/** + * Utility package of helper classes, mostly used in the implementation code. + */ +package com.rabbitmq.utility; \ No newline at end of file diff --git a/src/main/resources/META-INF/native-image/com.rabbitmq/amqp-client/native-image.properties b/src/main/resources/META-INF/native-image/com.rabbitmq/amqp-client/native-image.properties new file mode 100644 index 0000000000..f7d4484770 --- /dev/null +++ b/src/main/resources/META-INF/native-image/com.rabbitmq/amqp-client/native-image.properties @@ -0,0 +1 @@ +Args=-H:IncludeResources=rabbitmq-amqp-client.properties|version.properties diff --git a/src/main/resources/rabbitmq-amqp-client.properties b/src/main/resources/rabbitmq-amqp-client.properties new file mode 100644 index 0000000000..3562d483ec --- /dev/null +++ b/src/main/resources/rabbitmq-amqp-client.properties @@ -0,0 +1 @@ +com.rabbitmq.client.version = ${project.version} diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties new file mode 100644 index 0000000000..a13aa0c291 --- /dev/null +++ b/src/main/resources/version.properties @@ -0,0 +1,3 @@ +# here for backward compatibility +# use rabbitmq-amqp-client.properties to add or read properties +com.rabbitmq.client.version = ${project.version} diff --git a/src/main/scripts/generate_amqp_sources.groovy b/src/main/scripts/generate_amqp_sources.groovy new file mode 100644 index 0000000000..4a58e196ec --- /dev/null +++ b/src/main/scripts/generate_amqp_sources.groovy @@ -0,0 +1,60 @@ +import java.security.MessageDigest + +def md5(final file) { + MessageDigest digest = MessageDigest.getInstance("MD5") + file.withInputStream() { is -> + byte[] buffer = new byte[8192] + int read = 0 + while ((read = is.read(buffer)) > 0) { + digest.update(buffer, 0, read); + } + } + byte[] md5sum = digest.digest() + BigInteger bigInt = new BigInteger(1, md5sum) + return bigInt.toString(16) +} + +def generate_source(final type, final filename) { + String[] command = [ + 'python', + properties['script'], type, + properties['spec'], + filename + ] + + def pb = new ProcessBuilder(command) + pb.environment().put('PYTHONPATH', properties['codegen.dir']) + pb.redirectErrorStream(true) + + def process = pb.start() + process.waitFor() + if (process.exitValue() != 0) { + println(process.in.text.trim()) + fail("Failed to generate ${filename} with command: ${command.join(' ')}") + } +} + +def maybe_regen_source(final type, final filename) { + def file = new File(filename) + + if (file.exists()) { + def tmp_filename = filename + '.new' + def tmp_file = new File(tmp_filename) + + generate_source(type, tmp_filename) + old_md5 = md5(file) + new_md5 = md5(tmp_file) + + if (old_md5 == new_md5) { + tmp_file.delete() + } else { + tmp_file.renameTo(file) + } + } else { + generate_source(type, filename) + } + +} + +maybe_regen_source('header', properties['header']) +maybe_regen_source('body', properties['body']) diff --git a/src/test/java/SanityCheck.java b/src/test/java/SanityCheck.java new file mode 100755 index 0000000000..88b2fce079 --- /dev/null +++ b/src/test/java/SanityCheck.java @@ -0,0 +1,52 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//REPOS mavencentral,ossrh-staging=https://oss.sonatype.org/content/groups/staging/,rabbitmq-packagecloud-milestones=https://packagecloud.io/rabbitmq/maven-milestones/maven2 +//DEPS com.rabbitmq:amqp-client:${version} +//DEPS org.slf4j:slf4j-simple:1.7.36 + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.impl.ClientVersion; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SanityCheck { + + private static final Logger LOGGER = LoggerFactory.getLogger("rabbitmq"); + + public static void main(String[] args) { + try (Connection connection = new ConnectionFactory().newConnection()) { + Channel ch = connection.createChannel(); + String queue = ch.queueDeclare().getQueue(); + CountDownLatch latch = new CountDownLatch(1); + ch.basicConsume( + queue, + true, + new DefaultConsumer(ch) { + @Override + public void handleDelivery( + String consumerTag, + Envelope envelope, + AMQP.BasicProperties properties, + byte[] body) { + latch.countDown(); + } + }); + ch.basicPublish("", queue, null, "test".getBytes()); + boolean received = latch.await(5, TimeUnit.SECONDS); + if (!received) { + throw new IllegalStateException("Didn't receive message in 5 seconds"); + } + LOGGER.info("Test succeeded with Java client {}", ClientVersion.VERSION); + System.exit(0); + } catch (Exception e) { + LOGGER.info("Test failed with Java client {}", ClientVersion.VERSION, e); + System.exit(1); + } + } +} diff --git a/src/test/java/com/rabbitmq/client/AbstractJsonRpcTest.java b/src/test/java/com/rabbitmq/client/AbstractJsonRpcTest.java new file mode 100644 index 0000000000..6172a8f208 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/AbstractJsonRpcTest.java @@ -0,0 +1,214 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import com.rabbitmq.client.test.TestUtils; +import com.rabbitmq.tools.jsonrpc.JsonRpcClient; +import com.rabbitmq.tools.jsonrpc.JsonRpcMapper; +import com.rabbitmq.tools.jsonrpc.JsonRpcServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import java.util.Date; + +public abstract class AbstractJsonRpcTest { + + Connection clientConnection, serverConnection; + Channel clientChannel, serverChannel; + String queue = "json.rpc.queue"; + JsonRpcServer server; + JsonRpcClient client; + RpcService service; + + abstract JsonRpcMapper createMapper(); + + @BeforeEach + public void init() throws Exception { + clientConnection = TestUtils.connectionFactory().newConnection(); + clientChannel = clientConnection.createChannel(); + serverConnection = TestUtils.connectionFactory().newConnection(); + serverChannel = serverConnection.createChannel(); + serverChannel.queueDeclare(queue, false, false, false, null); + server = new JsonRpcServer(serverChannel, queue, RpcService.class, new DefaultRpcservice(), createMapper()); + new Thread(() -> { + try { + server.mainloop(); + } catch (Exception e) { + // safe to ignore when loops ends/server is canceled + } + }).start(); + client = new JsonRpcClient( + new RpcClientParams().channel(clientChannel).exchange("").routingKey(queue).timeout(1000), + createMapper() + ); + service = client.createProxy(RpcService.class); + } + + @AfterEach + public void tearDown() throws Exception { + if (server != null) { + server.terminateMainloop(); + } + if (client != null) { + client.close(); + } + if (serverChannel != null) { + serverChannel.queueDelete(queue); + } + clientConnection.close(); + serverConnection.close(); + } + + public interface RpcService { + + boolean procedurePrimitiveBoolean(boolean input); + + Boolean procedureBoolean(Boolean input); + + String procedureString(String input); + + String procedureStringString(String input1, String input2); + + int procedurePrimitiveInteger(int input); + + Integer procedureInteger(Integer input); + + Double procedureDouble(Double input); + + double procedurePrimitiveDouble(double input); + + Integer procedureLongToInteger(Long input); + + int procedurePrimitiveLongToInteger(long input); + + Long procedureLong(Long input); + + long procedurePrimitiveLong(long input); + + Pojo procedureIntegerToPojo(Integer id); + + String procedurePojoToString(Pojo pojo); + + void procedureException(); + + void procedureNoArgumentVoid(); + + Date procedureDateDate(Date date); + } + + public static class DefaultRpcservice implements RpcService { + + @Override + public boolean procedurePrimitiveBoolean(boolean input) { + return !input; + } + + @Override + public Boolean procedureBoolean(Boolean input) { + return Boolean.valueOf(!input.booleanValue()); + } + + @Override + public String procedureString(String input) { + return input + 1; + } + + @Override + public String procedureStringString(String input1, String input2) { + return input1 + input2; + } + + @Override + public int procedurePrimitiveInteger(int input) { + return input + 1; + } + + @Override + public Integer procedureInteger(Integer input) { + return input + 1; + } + + @Override + public Long procedureLong(Long input) { + return input + 1; + } + + @Override + public long procedurePrimitiveLong(long input) { + return input + 1L; + } + + @Override + public Double procedureDouble(Double input) { + return input + 1; + } + + @Override + public double procedurePrimitiveDouble(double input) { + return input + 1; + } + + @Override + public Integer procedureLongToInteger(Long input) { + return (int) (input + 1); + } + + @Override + public int procedurePrimitiveLongToInteger(long input) { + return (int) input + 1; + } + + @Override + public Pojo procedureIntegerToPojo(Integer id) { + Pojo pojo = new Pojo(); + pojo.setStringProperty(id.toString()); + return pojo; + } + + @Override + public String procedurePojoToString(Pojo pojo) { + return pojo.getStringProperty(); + } + + @Override + public void procedureException() { + throw new RuntimeException(); + } + + @Override + public void procedureNoArgumentVoid() { + + } + + @Override + public Date procedureDateDate(Date date) { + return date; + } + } + + public static class Pojo { + + private String stringProperty; + + public String getStringProperty() { + return stringProperty; + } + + public void setStringProperty(String stringProperty) { + this.stringProperty = stringProperty; + } + } +} diff --git a/src/test/java/com/rabbitmq/client/AmqpClientTestExtension.java b/src/test/java/com/rabbitmq/client/AmqpClientTestExtension.java new file mode 100644 index 0000000000..dcffa744b2 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/AmqpClientTestExtension.java @@ -0,0 +1,138 @@ +// Copyright (c) 2023-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom +// Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; + +import com.rabbitmq.client.test.TestUtils; +import com.rabbitmq.client.test.functional.FunctionalTestSuite; +import com.rabbitmq.client.test.server.HaTestSuite; +import com.rabbitmq.client.test.server.ServerTestSuite; +import com.rabbitmq.client.test.ssl.SslTestSuite; +import com.rabbitmq.tools.Host; +import java.net.Socket; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AmqpClientTestExtension + implements ExecutionCondition, BeforeAllCallback, BeforeEachCallback, AfterEachCallback { + + private static final Logger LOGGER = LoggerFactory.getLogger(AmqpClientTestExtension.class); + + private static boolean isFunctionalSuite(ExtensionContext context) { + return isTestSuite(context, FunctionalTestSuite.class); + } + + private static boolean isSslSuite(ExtensionContext context) { + return isTestSuite(context, SslTestSuite.class); + } + + private static boolean isServerSuite(ExtensionContext context) { + return isTestSuite(context, ServerTestSuite.class); + } + + private static boolean isHaSuite(ExtensionContext context) { + return isTestSuite(context, HaTestSuite.class); + } + + private static boolean isTestSuite(ExtensionContext context, Class clazz) { + return context.getUniqueId().contains(clazz.getName()); + } + + public static boolean requiredProperties() { + /* Path to rabbitmqctl. */ + String rabbitmqctl = Host.rabbitmqctlCommand(); + if (rabbitmqctl == null) { + System.err.println( + "rabbitmqctl required; please set \"rabbitmqctl.bin\" system" + " property"); + return false; + } + + return true; + } + + public static boolean isSSLAvailable() { + return checkServerListening("localhost", 5671); + } + + private static boolean checkServerListening(String host, int port) { + Socket s = null; + try { + s = new Socket(host, port); + return true; + } catch (Exception e) { + return false; + } finally { + if (s != null) { + try { + s.close(); + } catch (Exception e) { + } + } + } + } + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + // HA test suite must be checked first because it contains other test suites + if (isHaSuite(context)) { + return requiredProperties() + ? enabled("Required properties available") + : disabled("Required properties not available"); + } else if (isServerSuite(context)) { + return requiredProperties() + ? enabled("Required properties available") + : disabled("Required properties not available"); + } else if (isFunctionalSuite(context)) { + return requiredProperties() + ? enabled("Required properties available") + : disabled("Required properties not available"); + } else if (isSslSuite(context)) { + return requiredProperties() && isSSLAvailable() + ? enabled("Required properties and TLS available") + : disabled("Required properties or TLS not available"); + } + return enabled("ok"); + } + + @Override + public void beforeAll(ExtensionContext context) {} + + @Override + public void beforeEach(ExtensionContext context) { + LOGGER.info( + "Starting test: {}.{} (nio? {})", + context.getTestClass().get().getSimpleName(), + context.getTestMethod().get().getName(), + TestUtils.USE_NIO); + } + + @Override + public void afterEach(ExtensionContext context) { + LOGGER.info( + "Test finished: {}.{}", + context.getTestClass().get().getSimpleName(), + context.getTestMethod().get().getName()); + } +} diff --git a/src/test/java/com/rabbitmq/client/JacksonJsonRpcTest.java b/src/test/java/com/rabbitmq/client/JacksonJsonRpcTest.java new file mode 100644 index 0000000000..c8e8ebc829 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/JacksonJsonRpcTest.java @@ -0,0 +1,72 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client; + +import com.rabbitmq.tools.jsonrpc.JacksonJsonRpcMapper; +import com.rabbitmq.tools.jsonrpc.JsonRpcException; +import com.rabbitmq.tools.jsonrpc.JsonRpcMapper; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Calendar; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class JacksonJsonRpcTest extends AbstractJsonRpcTest { + + @Override + JsonRpcMapper createMapper() { + return new JacksonJsonRpcMapper(); + } + + @Test + public void rpc() { + assertFalse(service.procedurePrimitiveBoolean(true)); + assertFalse(service.procedureBoolean(Boolean.TRUE).booleanValue()); + assertEquals("hello1", service.procedureString("hello")); + assertEquals("hello1hello2", service.procedureStringString("hello1", "hello2")); + assertEquals(2, service.procedureInteger(1).intValue()); + assertEquals(2, service.procedurePrimitiveInteger(1)); + assertEquals(2, service.procedureDouble(1.0).intValue()); + assertEquals(2, (int) service.procedurePrimitiveDouble(1.0)); + assertEquals(2, (int) service.procedureLongToInteger(1L)); + assertEquals(2, service.procedurePrimitiveLongToInteger(1L)); + assertEquals(2, service.procedurePrimitiveLong(1L)); + assertEquals(2, service.procedureLong(1L).longValue()); + assertEquals("123", service.procedureIntegerToPojo(123).getStringProperty()); + service.procedureNoArgumentVoid(); + + Calendar calendar = Calendar.getInstance(); + Date date = calendar.getTime(); + Date returnedDate = service.procedureDateDate(date); + assertEquals(date.getTime(), returnedDate.getTime()); + + Pojo pojo = new Pojo(); + pojo.setStringProperty("hello"); + assertEquals("hello", service.procedurePojoToString(pojo)); + + try { + service.procedureException(); + fail("Remote procedure throwing exception, an exception should have been thrown"); + } catch (UndeclaredThrowableException e) { + assertTrue(e.getCause() instanceof JsonRpcException); + } + } +} diff --git a/src/com/rabbitmq/client/QueueingConsumer.java b/src/test/java/com/rabbitmq/client/QueueingConsumer.java similarity index 89% rename from src/com/rabbitmq/client/QueueingConsumer.java rename to src/test/java/com/rabbitmq/client/QueueingConsumer.java index d4e9b1fc4e..9da5f39057 100644 --- a/src/com/rabbitmq/client/QueueingConsumer.java +++ b/src/test/java/com/rabbitmq/client/QueueingConsumer.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client; @@ -27,7 +26,11 @@ /** * Convenience class: an implementation of {@link Consumer} with - * straightforward blocking semantics. + * straightforward blocking semantics. It is meant to be using in + * tests. + * + * Deprecated in favor of {@link DefaultConsumer} (see below for background). + * Will be removed in next major release. * * The general pattern for using QueueingConsumer is as follows: * @@ -56,9 +59,8 @@ * *

For a more complete example, see LogTail in the test/src/com/rabbitmq/examples * directory of the source distribution.

- *

* - *

Historical Perspective

+ *

Historical Perspective

* *

QueueingConsumer was introduced to allow * applications to overcome a limitation in the way Connection @@ -85,6 +87,7 @@ * As such, it is now safe to implement Consumer directly or * to extend DefaultConsumer and QueueingConsumer * is a lot less relevant.

+ * */ public class QueueingConsumer extends DefaultConsumer { private final BlockingQueue _queue; diff --git a/src/test/java/com/rabbitmq/client/impl/AMQConnectionRefreshCredentialsTest.java b/src/test/java/com/rabbitmq/client/impl/AMQConnectionRefreshCredentialsTest.java new file mode 100644 index 0000000000..d99a8434d6 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/impl/AMQConnectionRefreshCredentialsTest.java @@ -0,0 +1,184 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.rabbitmq.client.Method; +import com.rabbitmq.client.*; +import com.rabbitmq.client.observation.ObservationCollector; +import com.rabbitmq.client.test.TestUtils; +import com.rabbitmq.client.test.TestUtils.BrokerVersion; +import com.rabbitmq.client.test.TestUtils.BrokerVersionAtLeast; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@BrokerVersionAtLeast(BrokerVersion.RABBITMQ_3_8) +public class AMQConnectionRefreshCredentialsTest { + + @Mock + CredentialsProvider credentialsProvider; + + @Mock + CredentialsRefreshService refreshService; + + AutoCloseable mocks; + + @BeforeEach + void init() { + mocks = MockitoAnnotations.openMocks(this); + } + + @AfterEach + void tearDown() throws Exception { + mocks.close(); + } + + private static ConnectionFactory connectionFactoryThatSendsGarbageAfterUpdateSecret() { + ConnectionFactory cf = new ConnectionFactory() { + @Override + protected AMQConnection createConnection(ConnectionParams params, FrameHandler frameHandler, MetricsCollector metricsCollector) { + return new AMQConnection(params, frameHandler, metricsCollector, ObservationCollector.NO_OP) { + + @Override + AMQChannel createChannel0() { + return new AMQChannel(this, 0) { + @Override + public boolean processAsync(Command c) throws IOException { + return getConnection().processControlCommand(c); + } + + @Override + public AMQCommand rpc(Method m) throws IOException, ShutdownSignalException { + if (m instanceof UpdateSecretExtension.UpdateSecret) { + super.rpc(m); + return super.rpc(new UpdateSecretExtension.UpdateSecret(LongStringHelper.asLongString(""), "Refresh scheduled by client") { + @Override + public int protocolMethodId() { + return 255; + } + }); + } else { + return super.rpc(m); + } + + } + }; + + } + }; + } + }; + cf.setAutomaticRecoveryEnabled(false); + if (TestUtils.USE_NIO) { + cf.useNio(); + } + return cf; + } + + @Test + @SuppressWarnings("unchecked") + public void connectionIsUnregisteredFromRefreshServiceWhenClosed() throws Exception { + when(credentialsProvider.getUsername()).thenReturn("guest"); + when(credentialsProvider.getPassword()).thenReturn("guest"); + when(credentialsProvider.getTimeBeforeExpiration()).thenReturn(Duration.ofSeconds(10)); + + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.setCredentialsProvider(credentialsProvider); + + String registrationId = UUID.randomUUID().toString(); + CountDownLatch unregisteredLatch = new CountDownLatch(1); + + AtomicReference> refreshTokenCallable = new AtomicReference<>(); + when(refreshService.register(eq(credentialsProvider), any(Callable.class))).thenAnswer(invocation -> { + refreshTokenCallable.set(invocation.getArgument(1)); + return registrationId; + }); + doAnswer(invocation -> { + unregisteredLatch.countDown(); + return null; + }).when(refreshService).unregister(credentialsProvider, registrationId); + + cf.setCredentialsRefreshService(refreshService); + + verify(refreshService, never()).register(any(CredentialsProvider.class), any(Callable.class)); + try (Connection c = cf.newConnection()) { + verify(refreshService, times(1)).register(eq(credentialsProvider), any(Callable.class)); + Channel ch = c.createChannel(); + String queue = ch.queueDeclare().getQueue(); + TestUtils.sendAndConsumeMessage("", queue, queue, c); + verify(refreshService, never()).unregister(any(CredentialsProvider.class), anyString()); + // calling refresh + assertThat(refreshTokenCallable.get().call()).isTrue(); + } + verify(refreshService, times(1)).register(eq(credentialsProvider), any(Callable.class)); + assertThat(unregisteredLatch.await(5, TimeUnit.SECONDS)).isTrue(); + verify(refreshService, times(1)).unregister(credentialsProvider, registrationId); + } + + @Test + @SuppressWarnings("unchecked") + public void connectionIsUnregisteredFromRefreshServiceIfUpdateSecretFails() throws Exception { + when(credentialsProvider.getUsername()).thenReturn("guest"); + when(credentialsProvider.getPassword()).thenReturn("guest"); + when(credentialsProvider.getTimeBeforeExpiration()).thenReturn(Duration.ofSeconds(10)); + + ConnectionFactory cf = connectionFactoryThatSendsGarbageAfterUpdateSecret(); + cf.setCredentialsProvider(credentialsProvider); + + String registrationId = UUID.randomUUID().toString(); + CountDownLatch unregisteredLatch = new CountDownLatch(1); + AtomicReference> refreshTokenCallable = new AtomicReference<>(); + when(refreshService.register(eq(credentialsProvider), any(Callable.class))).thenAnswer(invocation -> { + refreshTokenCallable.set(invocation.getArgument(1)); + return registrationId; + }); + doAnswer(invocation -> { + unregisteredLatch.countDown(); + return null; + }).when(refreshService).unregister(credentialsProvider, registrationId); + + cf.setCredentialsRefreshService(refreshService); + + Connection c = cf.newConnection(); + verify(refreshService, times(1)).register(eq(credentialsProvider), any(Callable.class)); + Channel ch = c.createChannel(); + String queue = ch.queueDeclare().getQueue(); + TestUtils.sendAndConsumeMessage("", queue, queue, c); + verify(refreshService, never()).unregister(any(CredentialsProvider.class), anyString()); + + verify(refreshService, never()).unregister(any(CredentialsProvider.class), anyString()); + // calling refresh, this sends garbage and should make the broker close the connection + assertThat(refreshTokenCallable.get().call()).isFalse(); + assertThat(unregisteredLatch.await(5, TimeUnit.SECONDS)).isTrue(); + verify(refreshService, times(1)).unregister(credentialsProvider, registrationId); + assertThat(c.isOpen()).isFalse(); + } +} diff --git a/src/test/java/com/rabbitmq/client/impl/DefaultCredentialsRefreshServiceTest.java b/src/test/java/com/rabbitmq/client/impl/DefaultCredentialsRefreshServiceTest.java new file mode 100644 index 0000000000..e52a6d98c3 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/impl/DefaultCredentialsRefreshServiceTest.java @@ -0,0 +1,262 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import java.time.Duration; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.IntStream; + +import static com.rabbitmq.client.impl.DefaultCredentialsRefreshService.fixedDelayBeforeExpirationRefreshDelayStrategy; +import static com.rabbitmq.client.impl.DefaultCredentialsRefreshService.fixedTimeApproachingExpirationStrategy; +import static java.time.Duration.ofSeconds; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +public class DefaultCredentialsRefreshServiceTest { + + @Mock + Callable refreshAction; + + @Mock + CredentialsProvider credentialsProvider; + + DefaultCredentialsRefreshService refreshService; + + AutoCloseable mocks; + + @BeforeEach + void init() { + this.mocks = MockitoAnnotations.openMocks(this); + } + + @AfterEach + public void tearDown() throws Exception { + if (refreshService != null) { + refreshService.close(); + } + mocks.close(); + } + + @Test + public void scheduling() throws Exception { + refreshService = new DefaultCredentialsRefreshService.DefaultCredentialsRefreshServiceBuilder() + .refreshDelayStrategy(fixedDelayBeforeExpirationRefreshDelayStrategy(ofSeconds(2))) + .build(); + + AtomicInteger passwordSequence = new AtomicInteger(0); + when(credentialsProvider.getPassword()).thenAnswer( + (Answer) invocation -> "password-" + passwordSequence.get()); + when(credentialsProvider.getTimeBeforeExpiration()).thenAnswer((Answer) invocation -> ofSeconds(5)); + doAnswer(invocation -> { + passwordSequence.incrementAndGet(); + return null; + }).when(credentialsProvider).refresh(); + + List passwords = new CopyOnWriteArrayList<>(); + CountDownLatch latch = new CountDownLatch(2 * 2); + refreshAction = () -> { + passwords.add(credentialsProvider.getPassword()); + latch.countDown(); + return true; + }; + refreshService.register(credentialsProvider, refreshAction); + refreshService.register(credentialsProvider, refreshAction); + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(passwords).hasSize(4).containsExactlyInAnyOrder("password-1", "password-2", "password-1", "password-2"); + + AtomicInteger passwordSequence2 = new AtomicInteger(0); + CredentialsProvider credentialsProvider2 = mock(CredentialsProvider.class); + when(credentialsProvider2.getPassword()).thenAnswer((Answer) invocation -> "password2-" + passwordSequence2.get()); + when(credentialsProvider2.getTimeBeforeExpiration()).thenAnswer((Answer) invocation -> ofSeconds(4)); + doAnswer(invocation -> { + passwordSequence2.incrementAndGet(); + return null; + }).when(credentialsProvider2).refresh(); + + List passwords2 = new CopyOnWriteArrayList<>(); + CountDownLatch latch2 = new CountDownLatch(2 * 1); + refreshAction = () -> { + passwords2.add(credentialsProvider2.getPassword()); + latch2.countDown(); + return true; + }; + + refreshService.register(credentialsProvider2, refreshAction); + + assertThat(latch2.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(passwords2).hasSize(2).containsExactlyInAnyOrder( + "password2-1", "password2-2" + ); + assertThat(passwords).hasSizeGreaterThan(4); + } + + @Test + public void refreshActionIsCorrectlyRegisteredCalledAndCanceled() throws Exception { + DefaultCredentialsRefreshService.CredentialsProviderState state = new DefaultCredentialsRefreshService.CredentialsProviderState( + credentialsProvider + ); + when(refreshAction.call()).thenReturn(true); + state.add(new DefaultCredentialsRefreshService.Registration("1", refreshAction)); + + state.refresh(); + verify(credentialsProvider, times(1)).refresh(); + verify(refreshAction, times(1)).call(); + + state.refresh(); + verify(credentialsProvider, times(2)).refresh(); + verify(refreshAction, times(2)).call(); + + state.unregister("1"); + state.refresh(); + verify(credentialsProvider, times(3)).refresh(); + verify(refreshAction, times(2)).call(); + } + + @Test + public void refreshActionIsRemovedIfItReturnsFalse() throws Exception { + DefaultCredentialsRefreshService.CredentialsProviderState state = new DefaultCredentialsRefreshService.CredentialsProviderState( + credentialsProvider + ); + when(refreshAction.call()).thenReturn(false); + state.add(new DefaultCredentialsRefreshService.Registration("1", refreshAction)); + + state.refresh(); + verify(credentialsProvider, times(1)).refresh(); + verify(refreshAction, times(1)).call(); + + state.refresh(); + verify(credentialsProvider, times(2)).refresh(); + verify(refreshAction, times(1)).call(); + } + + @Test + public void refreshActionIsRemovedIfItErrorsTooMuch() throws Exception { + DefaultCredentialsRefreshService.CredentialsProviderState state = new DefaultCredentialsRefreshService.CredentialsProviderState( + credentialsProvider + ); + when(refreshAction.call()).thenThrow(RuntimeException.class); + state.add(new DefaultCredentialsRefreshService.Registration("1", refreshAction)); + + int callsCountBeforeCancellation = 5; + IntStream.range(0, callsCountBeforeCancellation).forEach(i -> state.refresh()); + + verify(credentialsProvider, times(callsCountBeforeCancellation)).refresh(); + verify(refreshAction, times(callsCountBeforeCancellation)).call(); + + state.refresh(); + verify(credentialsProvider, times(callsCountBeforeCancellation + 1)).refresh(); + verify(refreshAction, times(callsCountBeforeCancellation)).call(); + } + + @Test + public void errorInRefreshShouldBeRetried() throws Exception { + DefaultCredentialsRefreshService.CredentialsProviderState state = new DefaultCredentialsRefreshService.CredentialsProviderState( + credentialsProvider + ); + doThrow(RuntimeException.class).doThrow(RuntimeException.class) + .doNothing().when(credentialsProvider).refresh(); + + when(refreshAction.call()).thenReturn(true); + + state.add(new DefaultCredentialsRefreshService.Registration("1", refreshAction)); + + state.refresh(); + + verify(credentialsProvider, times(3)).refresh(); + verify(refreshAction, times(1)).call(); + } + + @Test + public void callbacksAreNotCalledWhenRetryOnRefreshIsExhausted() throws Exception { + DefaultCredentialsRefreshService.CredentialsProviderState state = new DefaultCredentialsRefreshService.CredentialsProviderState( + credentialsProvider + ); + doThrow(RuntimeException.class).when(credentialsProvider).refresh(); + + state.add(new DefaultCredentialsRefreshService.Registration("1", refreshAction)); + + state.refresh(); + + verify(credentialsProvider, times(3)).refresh(); + verify(refreshAction, times(0)).call(); + } + + @Test + public void refreshCanBeInterrupted() throws Exception { + DefaultCredentialsRefreshService.CredentialsProviderState state = new DefaultCredentialsRefreshService.CredentialsProviderState( + credentialsProvider + ); + + AtomicInteger callbackCount = new AtomicInteger(10); + when(refreshAction.call()).thenAnswer(invocation -> { + callbackCount.decrementAndGet(); + Thread.sleep(1000L); + return true; + }); + + IntStream.range(0, callbackCount.get()).forEach(i -> state.add(new DefaultCredentialsRefreshService.Registration(i + "", refreshAction))); + + Thread refreshThread = new Thread(() -> state.refresh()); + refreshThread.start(); + Thread.sleep(1000L); + refreshThread.interrupt(); + refreshThread.join(5000); + assertThat(refreshThread.isAlive()).isFalse(); + assertThat(callbackCount).hasValueGreaterThan(1); // not all the callbacks were called, because thread has been cancelled + } + + @Test + public void fixedDelayBeforeExpirationRefreshDelayStrategyTest() { + Function delayStrategy = fixedDelayBeforeExpirationRefreshDelayStrategy(ofSeconds(20)); + assertThat(delayStrategy.apply(ofSeconds(60))).as("refresh delay is TTL - fixed delay").isEqualTo(ofSeconds(40)); + assertThat(delayStrategy.apply(ofSeconds(10))).as("refresh delay is TTL if TTL < fixed delay").isEqualTo(ofSeconds(10)); + } + + @Test + public void fixedTimeApproachingExpirationStrategyTest() { + Function refreshStrategy = fixedTimeApproachingExpirationStrategy(ofSeconds(20)); + assertThat(refreshStrategy.apply(ofSeconds(60))).isFalse(); + assertThat(refreshStrategy.apply(ofSeconds(20))).isTrue(); + assertThat(refreshStrategy.apply(ofSeconds(19))).isTrue(); + assertThat(refreshStrategy.apply(ofSeconds(10))).isTrue(); + } + + @Test + public void ratioRefreshDelayStrategyTest() { + Function delayStrategy = DefaultCredentialsRefreshService.ratioRefreshDelayStrategy(0.8); + assertThat(delayStrategy.apply(ofSeconds(60))).isEqualTo(ofSeconds(48)); + assertThat(delayStrategy.apply(ofSeconds(30))).isEqualTo(ofSeconds(24)); + assertThat(delayStrategy.apply(ofSeconds(10))).isEqualTo(ofSeconds(8)); + assertThat(delayStrategy.apply(ofSeconds(5))).isEqualTo(ofSeconds(4)); + assertThat(delayStrategy.apply(ofSeconds(2))).isEqualTo(ofSeconds(1)); + assertThat(delayStrategy.apply(ofSeconds(1))).isEqualTo(ofSeconds(0)); + } + +} diff --git a/src/test/java/com/rabbitmq/client/impl/OAuth2ClientCredentialsGrantCredentialsProviderTest.java b/src/test/java/com/rabbitmq/client/impl/OAuth2ClientCredentialsGrantCredentialsProviderTest.java new file mode 100644 index 0000000000..6d210f3a8f --- /dev/null +++ b/src/test/java/com/rabbitmq/client/impl/OAuth2ClientCredentialsGrantCredentialsProviderTest.java @@ -0,0 +1,314 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import com.google.gson.Gson; +import com.rabbitmq.client.test.TestUtils; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.server.*; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OAuth2ClientCredentialsGrantCredentialsProviderTest { + + Server server; + + static boolean isJava13() { + String javaVersion = System.getProperty("java.version"); + return javaVersion != null && javaVersion.startsWith("13."); + } + + @BeforeEach + public void init() { + if (isJava13()) { + // for Java 13.0.7, see https://github.com/bcgit/bc-java/issues/941 + System.setProperty("keystore.pkcs12.keyProtectionAlgorithm", "PBEWithHmacSHA256AndAES_256"); + } + } + + @AfterEach + public void tearDown() throws Exception { + if (isJava13()) { + System.setProperty("keystore.pkcs12.keyProtectionAlgorithm", ""); + } + if (server != null) { + server.stop(); + } + } + + @Test + public void getToken() throws Exception { + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + int port = TestUtils.randomNetworkPort(); + connector.setPort(port); + server.setConnectors(new Connector[]{connector}); + + AtomicReference httpMethod = new AtomicReference<>(); + AtomicReference contentType = new AtomicReference<>(); + AtomicReference authorization = new AtomicReference<>(); + AtomicReference accept = new AtomicReference<>(); + AtomicReference accessToken = new AtomicReference<>(); + AtomicReference> httpParameters = new AtomicReference<>(); + + int expiresIn = 60; + + ContextHandler context = new ContextHandler(); + context.setContextPath("/uaa/oauth/token"); + context.setHandler(new AbstractHandler() { + + @Override + public void handle(String s, Request request, HttpServletRequest httpServletRequest, HttpServletResponse response) + throws IOException { + httpMethod.set(request.getMethod()); + contentType.set(request.getContentType()); + authorization.set(request.getHeader("authorization")); + accept.set(request.getHeader("accept")); + + accessToken.set(UUID.randomUUID().toString()); + + httpParameters.set(request.getParameterMap()); + + String json = sampleJsonToken(accessToken.get(), expiresIn); + + response.setStatus(HttpServletResponse.SC_OK); + response.setContentLength(json.length()); + response.setContentType("application/json"); + + response.getWriter().print(json); + + request.setHandled(true); + } + }); + + server.setHandler(context); + + server.setStopTimeout(1000); + server.start(); + + OAuth2ClientCredentialsGrantCredentialsProvider provider = new OAuth2ClientCredentialsGrantCredentialsProvider.OAuth2ClientCredentialsGrantCredentialsProviderBuilder() + .tokenEndpointUri("http://localhost:" + port + "/uaa/oauth/token/") + .clientId("rabbit_client").clientSecret("rabbit_secret") + .grantType("password") + .parameter("username", "rabbit_super") + .parameter("password", "rabbit_super") + .build(); + + String password = provider.getPassword(); + + assertThat(password).isEqualTo(accessToken.get()); + assertThat(provider.getTimeBeforeExpiration()).isBetween(Duration.ofSeconds(expiresIn - 10), Duration.ofSeconds(expiresIn + 10)); + + assertThat(httpMethod).hasValue("POST"); + assertThat(contentType).hasValue("application/x-www-form-urlencoded"); + assertThat(authorization).hasValue("Basic cmFiYml0X2NsaWVudDpyYWJiaXRfc2VjcmV0"); + assertThat(accept).hasValue("application/json"); + Map parameters = httpParameters.get(); + assertThat(parameters).isNotNull().hasSize(3).containsKeys("grant_type", "username", "password") + .hasEntrySatisfying("grant_type", v -> assertThat(v).hasSize(1).contains("password")) + .hasEntrySatisfying("username", v -> assertThat(v).hasSize(1).contains("rabbit_super")) + .hasEntrySatisfying("password", v -> assertThat(v).hasSize(1).contains("rabbit_super")); + } + + @Test + public void tls() throws Exception { + int port = TestUtils.randomNetworkPort(); + + String accessToken = UUID.randomUUID().toString(); + int expiresIn = 60; + + AbstractHandler httpHandler = new AbstractHandler() { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { + String json = sampleJsonToken(accessToken, expiresIn); + + response.setStatus(HttpServletResponse.SC_OK); + response.setContentLength(json.length()); + response.setContentType("application/json"); + + response.getWriter().print(json); + + baseRequest.setHandled(true); + } + }; + + KeyStore keyStore = startHttpsServer(port, httpHandler); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(keyStore); + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(null, tmf.getTrustManagers(), null); + + OAuth2ClientCredentialsGrantCredentialsProvider provider = new OAuth2ClientCredentialsGrantCredentialsProvider.OAuth2ClientCredentialsGrantCredentialsProviderBuilder() + .tokenEndpointUri("https://localhost:" + port + "/uaa/oauth/token/") + .clientId("rabbit_client").clientSecret("rabbit_secret") + .tls().sslContext(sslContext).builder() + .build(); + + String password = provider.getPassword(); + assertThat(password).isEqualTo(accessToken); + assertThat(provider.getTimeBeforeExpiration()).isBetween(Duration.ofSeconds(expiresIn - 10), Duration.ofSeconds(expiresIn + 10)); + } + + @Test + public void parseTokenDefault() { + OAuth2ClientCredentialsGrantCredentialsProvider provider = new OAuth2ClientCredentialsGrantCredentialsProvider( + "http://localhost:8080/uaa/oauth/token/", + "rabbit_client", "rabbit_secret", + "client_credentials" + ); + + String accessToken = "18c1b1dfdda04382a8bcc14d077b71dd"; + int expiresIn = 43199; + String response = sampleJsonToken(accessToken, expiresIn); + + OAuth2ClientCredentialsGrantCredentialsProvider.Token token = provider.parseToken(response); + assertThat(token.getAccess()).isEqualTo("18c1b1dfdda04382a8bcc14d077b71dd"); + assertThat(token.getTimeBeforeExpiration()).isBetween(Duration.ofSeconds(expiresIn - 10), Duration.ofSeconds(expiresIn + 1)); + } + + @Test + public void parseTokenGson() { + Gson gson = new Gson(); + OAuth2ClientCredentialsGrantCredentialsProvider provider = new OAuth2ClientCredentialsGrantCredentialsProvider( + "http://localhost:8080/uaa/oauth/token/", + "rabbit_client", "rabbit_secret", + "client_credentials" + ) { + @Override + protected Token parseToken(String response) { + try { + Map map = gson.fromJson(response, Map.class); + int expiresIn = ((Number) map.get("expires_in")).intValue(); + Instant receivedAt = Instant.now(); + return new Token(map.get("access_token").toString(), expiresIn, receivedAt); + } catch (Exception e) { + throw new OAuthTokenManagementException("Error while parsing OAuth 2 token", e); + } + } + }; + + String accessToken = "18c1b1dfdda04382a8bcc14d077b71dd"; + int expiresIn = 43199; + String response = sampleJsonToken(accessToken, expiresIn); + + OAuth2ClientCredentialsGrantCredentialsProvider.Token token = provider.parseToken(response); + assertThat(token.getAccess()).isEqualTo("18c1b1dfdda04382a8bcc14d077b71dd"); + assertThat(token.getTimeBeforeExpiration()).isBetween(Duration.ofSeconds(expiresIn - 10), Duration.ofSeconds(expiresIn + 1)); + } + + String sampleJsonToken(String accessToken, int expiresIn) { + String json = "{\n" + + " \"access_token\" : \"{accessToken}\",\n" + + " \"token_type\" : \"bearer\",\n" + + " \"expires_in\" : {expiresIn},\n" + + " \"scope\" : \"clients.read emails.write scim.userids password.write idps.write notifications.write oauth.login scim.write critical_notifications.write\",\n" + + " \"jti\" : \"18c1b1dfdda04382a8bcc14d077b71dd\"\n" + + "}"; + return json.replace("{accessToken}", accessToken).replace("{expiresIn}", expiresIn + ""); + } + + KeyStore startHttpsServer(int port, Handler handler) throws Exception { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + String keyStorePassword = "password"; + keyStore.load(null, keyStorePassword.toCharArray()); + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + JcaX509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder( + new X500NameBuilder().addRDN(BCStyle.CN, "localhost").build(), + BigInteger.valueOf(new SecureRandom().nextInt()), + Date.from(Instant.now().minus(10, ChronoUnit.DAYS)), + Date.from(Instant.now().plus(10, ChronoUnit.DAYS)), + new X500NameBuilder().addRDN(BCStyle.CN, "localhost").build(), + kp.getPublic() + ); + + X509CertificateHolder certificateHolder = certificateBuilder.build(new JcaContentSignerBuilder("SHA256WithRSAEncryption") + .build(kp.getPrivate())); + + X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(certificateHolder); + + keyStore.setKeyEntry("default", kp.getPrivate(), keyStorePassword.toCharArray(), new Certificate[]{certificate}); + + server = new Server(); + SslContextFactory sslContextFactory = new SslContextFactory.Server(); + sslContextFactory.setKeyStore(keyStore); + sslContextFactory.setKeyStorePassword(keyStorePassword); + + HttpConfiguration httpsConfiguration = new HttpConfiguration(); + httpsConfiguration.setSecureScheme("https"); + httpsConfiguration.setSecurePort(port); + httpsConfiguration.setOutputBufferSize(32768); + + SecureRequestCustomizer src = new SecureRequestCustomizer(); + src.setStsMaxAge(2000); + src.setStsIncludeSubDomains(true); + httpsConfiguration.addCustomizer(src); + + ServerConnector https = new ServerConnector(server, + new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), + new HttpConnectionFactory(httpsConfiguration)); + https.setPort(port); + https.setIdleTimeout(500000); + + server.setConnectors(new Connector[]{https}); + + ContextHandler context = new ContextHandler(); + context.setContextPath("/uaa/oauth/token"); + context.setHandler(handler); + + server.setHandler(context); + + server.start(); + return keyStore; + } + +} diff --git a/src/test/java/com/rabbitmq/client/impl/RefreshProtectedCredentialsProviderTest.java b/src/test/java/com/rabbitmq/client/impl/RefreshProtectedCredentialsProviderTest.java new file mode 100644 index 0000000000..8da70a0034 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/impl/RefreshProtectedCredentialsProviderTest.java @@ -0,0 +1,88 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RefreshProtectedCredentialsProviderTest { + + @Test + public void refresh() throws Exception { + AtomicInteger retrieveTokenCallCount = new AtomicInteger(0); + + RefreshProtectedCredentialsProvider credentialsProvider = new RefreshProtectedCredentialsProvider() { + + @Override + protected TestToken retrieveToken() { + retrieveTokenCallCount.incrementAndGet(); + try { + Thread.sleep(2000L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return new TestToken(UUID.randomUUID().toString()); + } + + @Override + protected String usernameFromToken(TestToken token) { + return ""; + } + + @Override + protected String passwordFromToken(TestToken token) { + return token.secret; + } + + @Override + protected Duration timeBeforeExpiration(TestToken token) { + return Duration.ofSeconds(1); + } + }; + + Set passwords = ConcurrentHashMap.newKeySet(); + CountDownLatch latch = new CountDownLatch(5); + IntStream.range(0, 5).forEach(i -> new Thread(() -> { + passwords.add(credentialsProvider.getPassword()); + latch.countDown(); + }).start()); + + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); + + assertThat(retrieveTokenCallCount).hasValue(1); + assertThat(passwords).hasSize(1); + } + + private static class TestToken { + + final String secret; + + TestToken(String secret) { + this.secret = secret; + } + } + +} diff --git a/src/test/java/com/rabbitmq/client/impl/ValueWriterTest.java b/src/test/java/com/rabbitmq/client/impl/ValueWriterTest.java new file mode 100644 index 0000000000..e402c1f868 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/impl/ValueWriterTest.java @@ -0,0 +1,68 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.impl; + +import org.junit.jupiter.api.Test; + +import java.io.*; +import java.math.BigDecimal; +import java.math.BigInteger; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class ValueWriterTest { + + @Test + public void writingOverlyLargeBigDecimalShouldFail() { + assertThatThrownBy(() -> { + OutputStream outputStream = new OutputStream() { + @Override + public void write(int b) { + } + }; + DataOutputStream dataOutputStream = new DataOutputStream(outputStream); + ValueWriter valueWriter = new ValueWriter(dataOutputStream); + valueWriter.writeFieldValue(new BigDecimal(Integer.MAX_VALUE).add(new BigDecimal(1))); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void writingOverlyLargeScaleInBigDecimalShouldFail() { + assertThatThrownBy(() -> { + OutputStream outputStream = new OutputStream() { + @Override + public void write(int b) { + } + }; + DataOutputStream dataOutputStream = new DataOutputStream(outputStream); + ValueWriter valueWriter = new ValueWriter(dataOutputStream); + valueWriter.writeFieldValue(new BigDecimal(BigInteger.ONE, 500)); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void bigDecimalWrittenAndReadMatches() throws IOException { + BigDecimal value = new BigDecimal(BigInteger.valueOf(56), 3); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutputStream = new DataOutputStream(outputStream); + ValueWriter valueWriter = new ValueWriter(dataOutputStream); + valueWriter.writeFieldValue(value); + + BigDecimal read = (BigDecimal) ValueReader.readFieldValue(new DataInputStream(new ByteArrayInputStream(outputStream.toByteArray()))); + assertThat(read).isEqualTo(value); + } +} diff --git a/test/src/com/rabbitmq/client/impl/WorkPoolTests.java b/src/test/java/com/rabbitmq/client/impl/WorkPoolTests.java similarity index 69% rename from test/src/com/rabbitmq/client/impl/WorkPoolTests.java rename to src/test/java/com/rabbitmq/client/impl/WorkPoolTests.java index 4137954864..d8cf881dea 100644 --- a/test/src/com/rabbitmq/client/impl/WorkPoolTests.java +++ b/src/test/java/com/rabbitmq/client/impl/WorkPoolTests.java @@ -1,30 +1,48 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.impl; -import junit.framework.TestCase; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Test; + /** * Unit tests for {@link WorkPool} */ -public class WorkPoolTests extends TestCase { +public class WorkPoolTests { - private final WorkPool pool = new WorkPool(); + private final WorkPool pool = new WorkPool(-1); /** * Test unknown key tolerated silently - * @throws Exception untested */ - public void testUnknownKey() throws Exception{ + @Test public void unknownKey() { assertFalse(this.pool.addWorkItem("test", new Object())); } /** * Test add work and remove work - * @throws Exception untested */ - public void testBasicInOut() throws Exception { + @Test public void basicInOut() { Object one = new Object(); Object two = new Object(); @@ -38,23 +56,21 @@ public void testBasicInOut() throws Exception { assertEquals(1, workList.size()); assertEquals(one, workList.get(0)); - assertTrue("Should be made ready", this.pool.finishWorkBlock(key)); + assertTrue(this.pool.finishWorkBlock(key), "Should be made ready"); workList.clear(); key = this.pool.nextWorkBlock(workList, 1); - assertEquals("Work client key wrong", "test", key); - assertEquals("Wrong work delivered", two, workList.get(0)); - - assertFalse("Should not be made ready after this.", this.pool.finishWorkBlock(key)); + assertEquals("test", key, "Work client key wrong"); + assertEquals(two, workList.get(0), "Wrong work delivered"); - assertNull("Shouldn't be more work", this.pool.nextWorkBlock(workList, 1)); + assertFalse(this.pool.finishWorkBlock(key), "Should not be made ready after this."); + assertNull(this.pool.nextWorkBlock(workList, 1), "Shouldn't be more work"); } /** * Test add work when work in progress. - * @throws Exception untested */ - public void testWorkInWhileInProgress() throws Exception { + @Test public void workInWhileInProgress() { Object one = new Object(); Object two = new Object(); @@ -80,9 +96,8 @@ public void testWorkInWhileInProgress() throws Exception { /** * Test multiple work keys. - * @throws Exception untested */ - public void testInterleavingKeys() throws Exception { + @Test public void interleavingKeys() { Object one = new Object(); Object two = new Object(); Object three = new Object(); @@ -111,9 +126,8 @@ public void testInterleavingKeys() throws Exception { /** * Test removal of key (with work) - * @throws Exception untested */ - public void testUnregisterKey() throws Exception { + @Test public void unregisterKey() { Object one = new Object(); Object two = new Object(); Object three = new Object(); @@ -136,9 +150,8 @@ public void testUnregisterKey() throws Exception { /** * Test removal of all keys (with work). - * @throws Exception untested */ - public void testUnregisterAllKeys() throws Exception { + @Test public void unregisterAllKeys() { Object one = new Object(); Object two = new Object(); Object three = new Object(); diff --git a/src/test/java/com/rabbitmq/client/impl/recovery/AutorecoveringChannelTest.java b/src/test/java/com/rabbitmq/client/impl/recovery/AutorecoveringChannelTest.java new file mode 100644 index 0000000000..bd72f31e47 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/impl/recovery/AutorecoveringChannelTest.java @@ -0,0 +1,48 @@ +package com.rabbitmq.client.impl.recovery; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public final class AutorecoveringChannelTest { + + private AutorecoveringChannel channel; + + @Mock + private AutorecoveringConnection autorecoveringConnection; + + @Mock + private RecoveryAwareChannelN recoveryAwareChannelN; + + @BeforeEach + void setup() { + MockitoAnnotations.openMocks(this); + this.channel = new AutorecoveringChannel(autorecoveringConnection, recoveryAwareChannelN); + } + + @Test + void abort() { + this.channel.abort(); + verify(recoveryAwareChannelN, times(1)).abort(); + } + + @Test + void abortWithDetails() { + int closeCode = 1; + String closeMessage = "reason"; + this.channel.abort(closeCode, closeMessage); + verify(recoveryAwareChannelN, times(1)).abort(closeCode, closeMessage); + } + + @Test + void abortWithDetailsCloseMessageNull() { + int closeCode = 1; + this.channel.abort(closeCode, null); + verify(recoveryAwareChannelN, times(1)).abort(closeCode, ""); + } + +} diff --git a/test/src/com/rabbitmq/client/test/AMQBuilderApiTest.java b/src/test/java/com/rabbitmq/client/test/AMQBuilderApiTest.java similarity index 53% rename from test/src/com/rabbitmq/client/test/AMQBuilderApiTest.java rename to src/test/java/com/rabbitmq/client/test/AMQBuilderApiTest.java index 20381a13f3..f2743f0f39 100644 --- a/test/src/com/rabbitmq/client/test/AMQBuilderApiTest.java +++ b/src/test/java/com/rabbitmq/client/test/AMQBuilderApiTest.java @@ -1,31 +1,34 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Method; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Method; + public class AMQBuilderApiTest extends BrokerTestCase { private static final String XCHG_NAME = "builder_test_xchg"; - public void testParticularBuilderForBasicSanityWithRpc() throws IOException + @Test public void particularBuilderForBasicSanityWithRpc() throws IOException { Method retVal = channel.rpc(new AMQP.Exchange.Declare.Builder() @@ -35,7 +38,7 @@ public void testParticularBuilderForBasicSanityWithRpc() throws IOException .build() ).getMethod(); - assertTrue("Channel should still be open.", channel.isOpen()); + assertTrue(channel.isOpen(), "Channel should still be open."); assertTrue(retVal instanceof AMQP.Exchange.DeclareOk); retVal = channel.rpc(new AMQP.Exchange.Delete.Builder() @@ -43,11 +46,11 @@ public void testParticularBuilderForBasicSanityWithRpc() throws IOException .build() ).getMethod(); - assertTrue("Channel should still be open.", channel.isOpen()); + assertTrue(channel.isOpen(), "Channel should still be open."); assertTrue(retVal instanceof AMQP.Exchange.DeleteOk); } - public void testParticularBuilderForBasicSanityWithAsyncRpc() throws IOException + @Test public void particularBuilderForBasicSanityWithAsyncRpc() throws IOException { channel.asyncRpc(new AMQP.Exchange.Declare.Builder() .exchange(XCHG_NAME) @@ -56,17 +59,17 @@ public void testParticularBuilderForBasicSanityWithAsyncRpc() throws IOException .build() ); - assertTrue("Channel should still be open.", channel.isOpen()); + assertTrue(channel.isOpen(), "Channel should still be open."); channel.asyncRpc(new AMQP.Exchange.Delete.Builder() .exchange(XCHG_NAME) .build() ); - assertTrue("Channel should still be open.", channel.isOpen()); + assertTrue(channel.isOpen(), "Channel should still be open."); } - public void testIllFormedBuilder() + @Test public void illFormedBuilder() { try { diff --git a/src/test/java/com/rabbitmq/client/test/AMQChannelTest.java b/src/test/java/com/rabbitmq/client/test/AMQChannelTest.java new file mode 100644 index 0000000000..f64e49a4fe --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/AMQChannelTest.java @@ -0,0 +1,177 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.ChannelContinuationTimeoutException; +import com.rabbitmq.client.Command; +import com.rabbitmq.client.Method; +import com.rabbitmq.client.TrafficListener; +import com.rabbitmq.client.impl.AMQChannel; +import com.rabbitmq.client.impl.AMQCommand; +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.AMQImpl; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.*; + +public class AMQChannelTest { + + ScheduledExecutorService scheduler; + + @BeforeEach public void init() { + scheduler = Executors.newSingleThreadScheduledExecutor(); + } + + @AfterEach public void tearDown() { + scheduler.shutdownNow(); + } + + @Test public void rpcTimesOutWhenResponseDoesNotCome() throws IOException { + int rpcTimeout = 100; + AMQConnection connection = mock(AMQConnection.class); + when(connection.getChannelRpcTimeout()).thenReturn(rpcTimeout); + when(connection.getTrafficListener()).thenReturn(TrafficListener.NO_OP); + + DummyAmqChannel channel = new DummyAmqChannel(connection, 1); + Method method = new AMQImpl.Queue.Declare.Builder() + .queue("") + .durable(false) + .exclusive(true) + .autoDelete(true) + .arguments(null) + .build(); + + try { + channel.rpc(method); + fail("Should time out and throw an exception"); + } catch(ChannelContinuationTimeoutException e) { + // OK + assertThat((DummyAmqChannel) e.getChannel()).isEqualTo(channel); + assertThat(e.getChannelNumber()).isEqualTo(channel.getChannelNumber()); + assertThat(e.getMethod()).isEqualTo(method); + assertThat(channel.nextOutstandingRpc()).as("outstanding RPC should have been cleaned").isNull(); + } + } + + @Test public void rpcReturnsResultWhenResponseHasCome() throws IOException { + int rpcTimeout = 1000; + AMQConnection connection = mock(AMQConnection.class); + when(connection.getChannelRpcTimeout()).thenReturn(rpcTimeout); + when(connection.getTrafficListener()).thenReturn(TrafficListener.NO_OP); + + final DummyAmqChannel channel = new DummyAmqChannel(connection, 1); + Method method = new AMQImpl.Queue.Declare.Builder() + .queue("") + .durable(false) + .exclusive(true) + .autoDelete(true) + .arguments(null) + .build(); + + final Method response = new AMQImpl.Queue.DeclareOk.Builder() + .queue("whatever") + .consumerCount(0) + .messageCount(0).build(); + + scheduler.schedule(new Callable() { + @Override + public Void call() throws Exception { + channel.handleCompleteInboundCommand(new AMQCommand(response)); + return null; + } + }, (long) (rpcTimeout / 2.0), TimeUnit.MILLISECONDS); + + AMQCommand rpcResponse = channel.rpc(method); + assertThat(rpcResponse.getMethod()).isEqualTo(response); + } + + @Test + public void testRpcTimeoutReplyComesDuringNexRpc() throws Exception { + int rpcTimeout = 100; + AMQConnection connection = mock(AMQConnection.class); + when(connection.getChannelRpcTimeout()).thenReturn(rpcTimeout); + when(connection.willCheckRpcResponseType()).thenReturn(Boolean.TRUE); + when(connection.getTrafficListener()).thenReturn(TrafficListener.NO_OP); + + final DummyAmqChannel channel = new DummyAmqChannel(connection, 1); + Method method = new AMQImpl.Queue.Declare.Builder() + .queue("123") + .durable(false) + .exclusive(true) + .autoDelete(true) + .arguments(null) + .build(); + + try { + channel.rpc(method); + fail("Should time out and throw an exception"); + } catch(final ChannelContinuationTimeoutException e) { + // OK + assertThat((DummyAmqChannel) e.getChannel()).isEqualTo(channel); + assertThat(e.getChannelNumber()).isEqualTo(channel.getChannelNumber()); + assertThat(e.getMethod()).isEqualTo(method); + assertThat(channel.nextOutstandingRpc()).as("outstanding RPC should have been cleaned").isNull(); + } + + // now do a basic.consume request and have the queue.declareok returned instead + method = new AMQImpl.Basic.Consume.Builder() + .queue("123") + .consumerTag("") + .arguments(null) + .build(); + + final Method response1 = new AMQImpl.Queue.DeclareOk.Builder() + .queue("123") + .consumerCount(0) + .messageCount(0).build(); + + final Method response2 = new AMQImpl.Basic.ConsumeOk.Builder() + .consumerTag("456").build(); + + scheduler.schedule((Callable) () -> { + channel.handleCompleteInboundCommand(new AMQCommand(response1)); + Thread.sleep(10); + channel.handleCompleteInboundCommand(new AMQCommand(response2)); + return null; + }, (long) (rpcTimeout / 2.0), TimeUnit.MILLISECONDS); + + AMQCommand rpcResponse = channel.rpc(method); + assertThat(rpcResponse.getMethod()).isEqualTo(response2); + } + + static class DummyAmqChannel extends AMQChannel { + + public DummyAmqChannel(AMQConnection connection, int channelNumber) { + super(connection, channelNumber); + } + + @Override + public boolean processAsync(Command command) throws IOException { + return false; + } + } + +} diff --git a/test/src/com/rabbitmq/client/test/AMQConnectionTest.java b/src/test/java/com/rabbitmq/client/test/AMQConnectionTest.java similarity index 60% rename from test/src/com/rabbitmq/client/test/AMQConnectionTest.java rename to src/test/java/com/rabbitmq/client/test/AMQConnectionTest.java index a22b2495be..a12a6d2a84 100644 --- a/test/src/com/rabbitmq/client/test/AMQConnectionTest.java +++ b/src/test/java/com/rabbitmq/client/test/AMQConnectionTest.java @@ -1,92 +1,118 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test; +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.ConnectionParams; +import com.rabbitmq.client.impl.Frame; +import com.rabbitmq.client.impl.FrameHandler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import java.io.IOException; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeoutException; -import com.rabbitmq.client.impl.ConnectionParams; -import com.rabbitmq.client.TopologyRecoveryException; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Consumer; -import com.rabbitmq.client.impl.AMQConnection; -import com.rabbitmq.client.ExceptionHandler; -import com.rabbitmq.client.impl.Frame; -import com.rabbitmq.client.impl.FrameHandler; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; /** * Test suite for AMQConnection. */ -public class AMQConnectionTest extends TestCase { +public class AMQConnectionTest { // private static final String CLOSE_MESSAGE = "terminated by test"; - /** - * Build a suite of tests - * @return the test suite for this class - */ - public static TestSuite suite() { - TestSuite suite = new TestSuite("connection"); - suite.addTestSuite(AMQConnectionTest.class); - return suite; - } - /** The mock frame handler used to test connection behaviour. */ private MockFrameHandler _mockFrameHandler; private ConnectionFactory factory; private MyExceptionHandler exceptionHandler; - /** Setup the environment for this test - * @see junit.framework.TestCase#setUp() - * @throws Exception if anything goes wrong - */ - @Override protected void setUp() throws Exception { - super.setUp(); + @BeforeEach public void setUp() { _mockFrameHandler = new MockFrameHandler(); - factory = new ConnectionFactory(); + factory = TestUtils.connectionFactory(); exceptionHandler = new MyExceptionHandler(); factory.setExceptionHandler(exceptionHandler); } - /** Tear down the environment for this test - * @see junit.framework.TestCase#tearDown() - * @throws Exception if anything goes wrong - */ - @Override protected void tearDown() throws Exception { + @AfterEach public void tearDown() { factory = null; _mockFrameHandler = null; - super.tearDown(); + } + + @Test public void negativeTCPConnectionTimeout() { + ConnectionFactory cf = TestUtils.connectionFactory(); + try { + cf.setConnectionTimeout(-10); + fail("expected an exception"); + } catch (IllegalArgumentException _ignored) { + // expected + } + } + + @Test public void negativeProtocolHandshakeTimeout() { + ConnectionFactory cf = TestUtils.connectionFactory(); + try { + cf.setHandshakeTimeout(-10); + fail("expected an exception"); + } catch (IllegalArgumentException _ignored) { + // expected + } + } + + @Test public void tcpConnectionTimeoutGreaterThanHandShakeTimeout() { + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.setHandshakeTimeout(3000); + cf.setConnectionTimeout(5000); + } + + @Test public void protocolHandshakeTimeoutGreaterThanTCPConnectionTimeout() { + ConnectionFactory cf = TestUtils.connectionFactory(); + + cf.setConnectionTimeout(5000); + cf.setHandshakeTimeout(7000); + + cf.setConnectionTimeout(0); + cf.setHandshakeTimeout(7000); + } + + @Test public void negativeRpcTimeoutIsForbidden() { + ConnectionFactory cf = TestUtils.connectionFactory(); + try { + cf.setChannelRpcTimeout(-10); + fail("expected an exception"); + } catch (IllegalArgumentException _ignored) { + // expected + } } /** Check the AMQConnection does send exactly 1 initial header, and deal correctly with * the frame handler throwing an exception when we try to read data */ - public void testConnectionSendsSingleHeaderAndTimesOut() { + @Test public void connectionSendsSingleHeaderAndTimesOut() throws TimeoutException { IOException exception = new SocketTimeoutException(); _mockFrameHandler.setExceptionOnReadingFrames(exception); assertEquals(0, _mockFrameHandler.countHeadersSent()); @@ -119,7 +145,7 @@ public void testConnectionSendsSingleHeaderAndTimesOut() { /** * Test that we catch timeout between connect and negotiation of the connection being finished. */ - public void testConnectionHangInNegotiation() { + @Test public void connectionHangInNegotiation() { this._mockFrameHandler.setTimeoutCount(10); // to limit hang assertEquals(0, this._mockFrameHandler.countHeadersSent()); try { @@ -127,13 +153,44 @@ public void testConnectionHangInNegotiation() { new AMQConnection(params, this._mockFrameHandler).start(); fail("Connection should have thrown exception"); } catch(IOException signal) { - // As expected + // expected + } catch(TimeoutException te) { + // also fine: continuation timed out first } assertEquals(1, this._mockFrameHandler.countHeadersSent()); - // _connection.close(0, CLOSE_MESSAGE); List exceptionList = exceptionHandler.getHandledExceptions(); - assertEquals("Only one exception expected", 1, exceptionList.size()); - assertEquals("Wrong type of exception returned.", SocketTimeoutException.class, exceptionList.get(0).getClass()); + assertEquals(1, exceptionList.size(), "Only one exception expected"); + assertEquals(SocketTimeoutException.class, exceptionList.get(0).getClass(), "Wrong type of exception returned."); + } + + @Test public void clientProvidedConnectionName() throws IOException, TimeoutException { + String providedName = "event consumers connection"; + Connection connection = factory.newConnection(providedName); + assertEquals(providedName, connection.getClientProvidedName()); + connection.close(); + + List
addrs1 = Arrays.asList(new Address("127.0.0.1"), new Address("127.0.0.1", 5672)); + connection = factory.newConnection(addrs1, providedName); + assertEquals(providedName, connection.getClientProvidedName()); + connection.close(); + + Address[] addrs2 = {new Address("127.0.0.1"), new Address("127.0.0.1", 5672)}; + connection = factory.newConnection(addrs2, providedName); + assertEquals(providedName, connection.getClientProvidedName()); + connection.close(); + + ExecutorService xs = Executors.newSingleThreadExecutor(); + connection = factory.newConnection(xs, providedName); + assertEquals(providedName, connection.getClientProvidedName()); + connection.close(); + + connection = factory.newConnection(xs, addrs1, providedName); + assertEquals(providedName, connection.getClientProvidedName()); + connection.close(); + + connection = factory.newConnection(xs, addrs2, providedName); + assertEquals(providedName, connection.getClientProvidedName()); + connection.close(); } /** Mock frame handler to facilitate testing. */ @@ -178,6 +235,11 @@ public void sendHeader() throws IOException { _numHeadersSent++; } + @Override + public void initialize(AMQConnection connection) { + connection.startMainLoop(); + } + public void setTimeout(int timeoutMs) throws SocketException { this.timeout = timeoutMs; } diff --git a/src/test/java/com/rabbitmq/client/test/AddressTest.java b/src/test/java/com/rabbitmq/client/test/AddressTest.java new file mode 100644 index 0000000000..d8101c63c8 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/AddressTest.java @@ -0,0 +1,92 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Address; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +public class AddressTest { + + @Test public void isHostWithPort() { + assertTrue(Address.isHostWithPort("127.0.0.1:5672")); + assertTrue(Address.isHostWithPort("[1080:0:0:0:8:800:200C:417A]:5672")); + assertTrue(Address.isHostWithPort("[::1]:5672")); + + assertFalse(Address.isHostWithPort("127.0.0.1")); + assertFalse(Address.isHostWithPort("[1080:0:0:0:8:800:200C:417A]")); + assertFalse(Address.isHostWithPort("[::1]")); + } + + @Test public void parseHost() { + assertEquals("127.0.0.1", Address.parseHost("127.0.0.1:5672")); + assertEquals("[1080:0:0:0:8:800:200C:417A]", Address.parseHost("[1080:0:0:0:8:800:200C:417A]:5673")); + assertEquals("[::1]", Address.parseHost("[::1]:5672")); + + assertEquals("127.0.0.1", Address.parseHost("127.0.0.1")); + assertEquals("[1080:0:0:0:8:800:200C:417A]", Address.parseHost("[1080:0:0:0:8:800:200C:417A]")); + assertEquals("[::1]", Address.parseHost("[::1]")); + } + + @Test public void parsePort() { + assertEquals(5672, Address.parsePort("127.0.0.1:5672")); + assertEquals(5673, Address.parsePort("[1080:0:0:0:8:800:200C:417A]:5673")); + assertEquals(5672, Address.parsePort("[::1]:5672")); + + // "use default port" value + assertEquals(-1, Address.parsePort("127.0.0.1")); + assertEquals(-1, Address.parsePort("[1080:0:0:0:8:800:200C:417A]")); + assertEquals(-1, Address.parsePort("[::1]")); + } + + @Test public void parseIPv4() { + assertEquals(addr("192.168.1.10"), Address.parseAddress("192.168.1.10")); + assertEquals(addr("192.168.1.10", 5682), Address.parseAddress("192.168.1.10:5682")); + } + + @Test public void parseIPv6() { + // quoted IPv6 addresses without a port + assertEquals(addr("[1080:0:0:0:8:800:200C:417A]"), Address.parseAddress("[1080:0:0:0:8:800:200C:417A]")); + assertEquals(addr("[::1]"), Address.parseAddress("[::1]")); + + // quoted IPv6 addresses with a port + assertEquals(addr("[1080:0:0:0:8:800:200C:417A]", 5673), Address.parseAddress("[1080:0:0:0:8:800:200C:417A]:5673")); + assertEquals(addr("[::1]", 5673), Address.parseAddress("[::1]:5673")); + } + + @Test + public void parseUnquotedIPv6() { + // using a non-quoted IPv6 addresses with a port + Assertions.assertThatThrownBy(() -> Address.parseAddress("::1:5673")) + .isInstanceOf(IllegalArgumentException.class); + } + + private Address addr(String addr) { + return new Address(addr); + } + + private Address addr(String addr, int port) { + return new Address(addr, port); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/AmqpUriTest.java b/src/test/java/com/rabbitmq/client/test/AmqpUriTest.java new file mode 100644 index 0000000000..c1670b2250 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/AmqpUriTest.java @@ -0,0 +1,169 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.ConnectionFactory; + +public class AmqpUriTest +{ + @Test public void uriParsing() + throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException + { + /* From the spec (subset of the tests) */ + parseSuccess("amqp://user:pass@host:10000/vhost", + "user", "pass", "host", 10000, "vhost", false); + parseSuccess("aMQps://user%61:%61pass@host:10000/v%2fhost", + "usera", "apass", "host", 10000, "v/host", true); + parseSuccess("amqp://host", "guest", "guest", "host", 5672, "/", false); + parseSuccess("amqp:///vhost", + "guest", "guest", "localhost", 5672, "vhost", false); + parseSuccess("amqp://host/", "guest", "guest", "host", 5672, "", false); + parseSuccess("amqp://host/%2f", "guest", "guest", "host", 5672, "/", false); + parseSuccess("amqp://[::1]", "guest", "guest", "[::1]", 5672, "/", false); + + /* Various other success cases */ + parseSuccess("amqp://host:100", "guest", "guest", "host", 100, "/", false); + parseSuccess("amqp://[::1]:100", "guest", "guest", "[::1]", 100, "/", false); + + parseSuccess("amqp://host/blah", + "guest", "guest", "host", 5672, "blah", false); + parseSuccess("amqp://host:100/blah", + "guest", "guest", "host", 100, "blah", false); + parseSuccess("amqp://[::1]/blah", + "guest", "guest", "[::1]", 5672, "blah", false); + parseSuccess("amqp://[::1]:100/blah", + "guest", "guest", "[::1]", 100, "blah", false); + + parseSuccess("amqp://user:pass@host", + "user", "pass", "host", 5672, "/", false); + parseSuccess("amqp://user:pass@[::1]", + "user", "pass", "[::1]", 5672, "/", false); + parseSuccess("amqp://user:pass@[::1]:100", + "user", "pass", "[::1]", 100, "/", false); + + /* using query parameters */ + parseSuccess("amqp://user:pass@host:10000/vhost?", + "user", "pass", "host", 10000, "vhost", false); + parseSuccess("amqp://user:pass@host:10000/vhost?&", + "user", "pass", "host", 10000, "vhost", false); + parseSuccess("amqp://user:pass@host:10000/vhost?unknown_parameter", + "user", "pass", "host", 10000, "vhost", false); + parseSuccess("amqp://user:pass@host:10000/vhost?unknown_parameter=value", + "user", "pass", "host", 10000, "vhost", false); + parseSuccess("amqp://user:pass@host:10000/vhost?unknown%2fparameter=value", + "user", "pass", "host", 10000, "vhost", false); + + parseSuccess("amqp://user:pass@host:10000/vhost?heartbeat=342", + "user", "pass", "host", 10000, "vhost", false, + 342, null, null); + parseSuccess("amqp://user:pass@host:10000/vhost?connection_timeout=442", + "user", "pass", "host", 10000, "vhost", false, + null, 442, null); + parseSuccess("amqp://user:pass@host:10000/vhost?channel_max=542", + "user", "pass", "host", 10000, "vhost", false, + null, null, 542); + parseSuccess("amqp://user:pass@host:10000/vhost?heartbeat=342&connection_timeout=442&channel_max=542", + "user", "pass", "host", 10000, "vhost", false, + 342, 442, 542); + parseSuccess("amqp://user:pass@host:10000/vhost?heartbeat=342&connection_timeout=442&channel_max=542&a=b", + "user", "pass", "host", 10000, "vhost", false, + 342, 442, 542); + + /* Various failure cases */ + parseFail("https://www.rabbitmq.com"); + parseFail("amqp://foo[::1]"); + parseFail("amqp://foo:[::1]"); + parseFail("amqp://[::1]foo"); + + parseFail("amqp://foo%1"); + parseFail("amqp://foo%1x"); + parseFail("amqp://foo%xy"); + + parseFail("amqp://user:pass@host:10000/vhost?heartbeat=not_an_integer"); + parseFail("amqp://user:pass@host:10000/vhost?heartbeat=-1"); + parseFail("amqp://user:pass@host:10000/vhost?connection_timeout=not_an_integer"); + parseFail("amqp://user:pass@host:10000/vhost?connection_timeout=-1"); + parseFail("amqp://user:pass@host:10000/vhost?channel_max=not_an_integer"); + parseFail("amqp://user:pass@host:10000/vhost?channel_max=-1"); + parseFail("amqp://user:pass@host:10000/vhost?heartbeat=342?connection_timeout=442"); + } + + @Test + public void processUriQueryParameterShouldBeCalledForNotHandledParameter() throws Exception { + Map processedParameters = new HashMap<>(); + ConnectionFactory cf = new ConnectionFactory() { + @Override + protected void processUriQueryParameter(String key, String value) { + processedParameters.put(key, value); + } + }; + cf.setUri("amqp://user:pass@host:10000/vhost?heartbeat=60&key=value"); + assertThat(processedParameters).hasSize(1).containsEntry("key", "value"); + } + + private void parseSuccess(String uri, String user, String password, + String host, int port, String vhost, boolean secured) + throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException + { + parseSuccess(uri, user, password, host, port, vhost, secured, null, null, null); + } + + private void parseSuccess(String uri, String user, String password, + String host, int port, String vhost, boolean secured, + Integer heartbeat, Integer connectionTimeout, Integer channelMax) + throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException + { + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.setUri(uri); + + assertEquals(user, cf.getUsername()); + assertEquals(password, cf.getPassword()); + assertEquals(host, cf.getHost()); + assertEquals(port, cf.getPort()); + assertEquals(vhost, cf.getVirtualHost()); + assertEquals(secured, cf.isSSL()); + + if(heartbeat != null) { + assertEquals(heartbeat.intValue(), cf.getRequestedHeartbeat()); + } + if(connectionTimeout != null) { + assertEquals(connectionTimeout.intValue(), cf.getConnectionTimeout()); + } + if(channelMax != null) { + assertEquals(channelMax.intValue(), cf.getRequestedChannelMax()); + } + } + + private void parseFail(String uri) { + try { + (TestUtils.connectionFactory()).setUri(uri); + fail("URI parse didn't fail: '" + uri + "'"); + } catch (Exception e) { + // whoosh! + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/BlockedConnectionTest.java b/src/test/java/com/rabbitmq/client/test/BlockedConnectionTest.java new file mode 100644 index 0000000000..5c757fc606 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/BlockedConnectionTest.java @@ -0,0 +1,62 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import static com.rabbitmq.client.test.TestUtils.LatchConditions.completed; +import static com.rabbitmq.client.test.TestUtils.waitAtMost; +import static org.assertj.core.api.Assertions.assertThat; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import java.util.concurrent.CountDownLatch; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class BlockedConnectionTest extends BrokerTestCase { + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void errorInBlockListenerShouldCloseConnection(boolean nio) throws Exception { + ConnectionFactory cf = TestUtils.connectionFactory(); + if (nio) { + cf.useNio(); + } else { + cf.useBlockingIo(); + } + Connection c = cf.newConnection(); + CountDownLatch shutdownLatch = new CountDownLatch(1); + c.addShutdownListener(cause -> shutdownLatch.countDown()); + CountDownLatch blockedLatch = new CountDownLatch(1); + c.addBlockedListener( + reason -> { + blockedLatch.countDown(); + throw new RuntimeException("error in blocked listener!"); + }, + () -> {}); + try { + block(); + Channel ch = c.createChannel(); + ch.basicPublish("", "", null, "dummy".getBytes()); + assertThat(blockedLatch).is(completed()); + } finally { + unblock(); + } + assertThat(shutdownLatch).is(completed()); + waitAtMost(() -> !c.isOpen()); + } + +} diff --git a/test/src/com/rabbitmq/client/test/BlockingCellTest.java b/src/test/java/com/rabbitmq/client/test/BlockingCellTest.java similarity index 74% rename from test/src/com/rabbitmq/client/test/BlockingCellTest.java rename to src/test/java/com/rabbitmq/client/test/BlockingCellTest.java index 267f6dd06f..ef2a6fd3cf 100644 --- a/test/src/com/rabbitmq/client/test/BlockingCellTest.java +++ b/src/test/java/com/rabbitmq/client/test/BlockingCellTest.java @@ -1,54 +1,47 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test; +import com.rabbitmq.utility.BlockingCell; +import org.junit.jupiter.api.Test; + import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -import com.rabbitmq.utility.BlockingCell; +import static org.junit.jupiter.api.Assertions.*; public class BlockingCellTest - extends TestCase { - public static TestSuite suite() - { - TestSuite suite = new TestSuite("blockingCells"); - suite.addTestSuite(BlockingCellTest.class); - return suite; - } - public void testDoubleSet() throws InterruptedException + @Test + public void doubleSet() throws InterruptedException { BlockingCell cell = new BlockingCell(); cell.set("one"); assertEquals("one", cell.get()); try { cell.set("two"); - } catch (AssertionError ae) { + } catch (IllegalStateException ae) { return; } - fail("Expected AssertionError"); + fail("Expected IllegalStateException"); } - public void testMultiGet() + @Test public void multiGet() throws InterruptedException { final BlockingCell cell = new BlockingCell(); @@ -57,7 +50,7 @@ public void testMultiGet() assertEquals("one", cell.get()); } - public void testNullSet() + @Test public void nullSet() throws InterruptedException { BlockingCell c = new BlockingCell(); @@ -65,7 +58,7 @@ public void testNullSet() assertNull(c.get()); } - public void testEarlySet() + @Test public void earlySet() throws InterruptedException { final BlockingCell cell = new BlockingCell(); @@ -96,7 +89,7 @@ public void run() { assertEquals("hello", holder.get()); } - public void testLateSet() + @Test public void lateSet() throws InterruptedException { final BlockingCell cell = new BlockingCell(); @@ -130,8 +123,8 @@ public void run() { assertEquals("hello", holder.get()); } - - public void testGetWaitsUntilSet() throws InterruptedException { + + @Test public void getWaitsUntilSet() throws InterruptedException { final BlockingCell cell = new BlockingCell(); final String value = "foo"; final AtomicReference valueHolder = new AtomicReference(); @@ -156,7 +149,7 @@ public void testGetWaitsUntilSet() throws InterruptedException { assertTrue(value == valueHolder.get()); } - public void testSetIfUnset() throws InterruptedException { + @Test public void setIfUnset() throws InterruptedException { final BlockingCell cell = new BlockingCell(); assertTrue(cell.setIfUnset("foo")); assertEquals("foo", cell.get()); diff --git a/test/src/com/rabbitmq/client/test/BrokenFramesTest.java b/src/test/java/com/rabbitmq/client/test/BrokenFramesTest.java similarity index 69% rename from test/src/com/rabbitmq/client/test/BrokenFramesTest.java rename to src/test/java/com/rabbitmq/client/test/BrokenFramesTest.java index 7e57ffec7b..331225953b 100644 --- a/test/src/com/rabbitmq/client/test/BrokenFramesTest.java +++ b/src/test/java/com/rabbitmq/client/test/BrokenFramesTest.java @@ -1,22 +1,32 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.UnexpectedFrameError; +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.AMQImpl.Basic.Publish; +import com.rabbitmq.client.impl.Frame; +import com.rabbitmq.client.impl.FrameHandler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import java.io.IOException; import java.net.InetAddress; import java.net.SocketException; @@ -25,42 +35,24 @@ import java.util.List; import java.util.concurrent.Executors; -import junit.framework.TestCase; -import junit.framework.TestSuite; +import static org.junit.jupiter.api.Assertions.*; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.UnexpectedFrameError; -import com.rabbitmq.client.impl.AMQConnection; -import com.rabbitmq.client.impl.Frame; -import com.rabbitmq.client.impl.FrameHandler; -import com.rabbitmq.client.impl.AMQImpl.Basic.Publish; - -public class BrokenFramesTest extends TestCase { - public static TestSuite suite() { - TestSuite suite = new TestSuite("connection"); - suite.addTestSuite(BrokenFramesTest.class); - return suite; - } +public class BrokenFramesTest { private MyFrameHandler myFrameHandler; private ConnectionFactory factory; - @Override - protected void setUp() throws Exception { - super.setUp(); + @BeforeEach public void setUp() { myFrameHandler = new MyFrameHandler(); - factory = new ConnectionFactory(); + factory = TestUtils.connectionFactory(); } - @Override - protected void tearDown() throws Exception { + @AfterEach public void tearDown() { factory = null; myFrameHandler = null; - super.tearDown(); } - public void testNoMethod() throws Exception { + @Test public void noMethod() throws Exception { List frames = new ArrayList(); frames.add(new Frame(AMQP.FRAME_HEADER, 0)); myFrameHandler.setFrames(frames.iterator()); @@ -70,7 +62,7 @@ public void testNoMethod() throws Exception { } catch (IOException e) { UnexpectedFrameError unexpectedFrameError = findUnexpectedFrameError(e); assertNotNull(unexpectedFrameError); - assertEquals(AMQP.FRAME_HEADER, unexpectedFrameError.getReceivedFrame().type); + assertEquals(AMQP.FRAME_HEADER, unexpectedFrameError.getReceivedFrame().getType()); assertEquals(AMQP.FRAME_METHOD, unexpectedFrameError.getExpectedFrameType()); return; } @@ -78,7 +70,7 @@ public void testNoMethod() throws Exception { fail("No UnexpectedFrameError thrown"); } - public void testMethodThenBody() throws Exception { + @Test public void methodThenBody() throws Exception { List frames = new ArrayList(); byte[] contentBody = new byte[10]; @@ -96,7 +88,7 @@ public void testMethodThenBody() throws Exception { } catch (IOException e) { UnexpectedFrameError unexpectedFrameError = findUnexpectedFrameError(e); assertNotNull(unexpectedFrameError); - assertEquals(AMQP.FRAME_BODY, unexpectedFrameError.getReceivedFrame().type); + assertEquals(AMQP.FRAME_BODY, unexpectedFrameError.getReceivedFrame().getType()); assertEquals(AMQP.FRAME_HEADER, unexpectedFrameError.getExpectedFrameType()); return; } @@ -117,19 +109,24 @@ private UnexpectedFrameError findUnexpectedFrameError(Exception e) { } private static class MyFrameHandler implements FrameHandler { - private Iterator frames; + private Iterator frames; - public void setFrames(Iterator frames) { - this.frames = frames; - } + public void setFrames(Iterator frames) { + this.frames = frames; + } - public Frame readFrame() throws IOException { - return frames.next(); + public Frame readFrame() throws IOException { + return frames.next(); } public void sendHeader() throws IOException { } + @Override + public void initialize(AMQConnection connection) { + connection.startMainLoop(); + } + public void setTimeout(int timeoutMs) throws SocketException { // no need to implement this: don't bother changing the timeout } diff --git a/test/src/com/rabbitmq/client/test/BrokerTestCase.java b/src/test/java/com/rabbitmq/client/test/BrokerTestCase.java similarity index 62% rename from test/src/com/rabbitmq/client/test/BrokerTestCase.java rename to src/test/java/com/rabbitmq/client/test/BrokerTestCase.java index 2521e1f19b..ea453d309b 100644 --- a/test/src/com/rabbitmq/client/test/BrokerTestCase.java +++ b/src/test/java/com/rabbitmq/client/test/BrokerTestCase.java @@ -1,69 +1,100 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test; +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.nio.NioParams; +import com.rabbitmq.tools.Host; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; + import java.io.IOException; import java.util.Map; import java.util.UUID; +import java.util.concurrent.TimeoutException; -import junit.framework.TestCase; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Command; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.Method; -import com.rabbitmq.client.ShutdownSignalException; -import com.rabbitmq.client.AlreadyClosedException; -import com.rabbitmq.client.impl.ShutdownNotifierComponent; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.tools.Host; +import static com.rabbitmq.client.test.TestUtils.currentVersion; +import static com.rabbitmq.client.test.TestUtils.versionCompare; +import static org.junit.jupiter.api.Assertions.*; + +public class BrokerTestCase { + + private String brokerVersion; + + protected volatile TestInfo testInfo; -public class BrokerTestCase extends TestCase { protected ConnectionFactory connectionFactory = newConnectionFactory(); protected ConnectionFactory newConnectionFactory() { - return new ConnectionFactory(); + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + if(TestUtils.USE_NIO) { + connectionFactory.setNioParams(nioParams()); + } + connectionFactory.setAutomaticRecoveryEnabled(isAutomaticRecoveryEnabled()); + return connectionFactory; + } + + protected NioParams nioParams() { + return new NioParams(); + } + + protected boolean isAutomaticRecoveryEnabled() { + return true; } protected Connection connection; protected Channel channel; - protected void setUp() - throws IOException { + @BeforeEach + public void setUp(TestInfo testInfo) throws IOException, TimeoutException { + Assumptions.assumeTrue(shouldRun()); + this.testInfo = testInfo; openConnection(); + if (this.connection != null) { + this.brokerVersion = currentVersion(this.connection.getServerProperties().get("version").toString()); + } openChannel(); createResources(); } - protected void tearDown() - throws IOException { - closeChannel(); - closeConnection(); + @AfterEach public void tearDown(TestInfo testInfo) + throws IOException, TimeoutException { + if(shouldRun()) { + closeChannel(); + closeConnection(); - openConnection(); - openChannel(); - releaseResources(); - closeChannel(); - closeConnection(); + openConnection(); + openChannel(); + releaseResources(); + closeChannel(); + closeConnection(); + } + } + + /** + * Whether to run the test or not. + * Subclasses can check whether some broker features + * are available or not, and choose not to run the test. + * @return + */ + protected boolean shouldRun() throws IOException { + return true; } /** @@ -72,7 +103,7 @@ protected void tearDown() * connection and channel have been opened. */ protected void createResources() - throws IOException { + throws IOException, TimeoutException { } /** @@ -87,21 +118,22 @@ protected void releaseResources() } protected void restart() - throws IOException { - tearDown(); + throws IOException, TimeoutException { + tearDown(this.testInfo); bareRestart(); - setUp(); + setUp(this.testInfo); } protected void bareRestart() throws IOException { - Host.invokeMakeTarget("restart-app"); + Host.stopRabbitOnNode(); + Host.startRabbitOnNode(); } public void openConnection() - throws IOException { + throws IOException, TimeoutException { if (connection == null) { - connection = connectionFactory.newConnection(); + connection = connectionFactory.newConnection(UUID.randomUUID().toString()); } } @@ -135,7 +167,6 @@ public void checkShutdownSignal(int expectedCode, ShutdownSignalException sse) { Method method = sse.getReason(); channel = null; if (sse.isHardError()) { - connection = null; AMQP.Connection.Close closeMethod = (AMQP.Connection.Close) method; assertEquals(expectedCode, closeMethod.getReplyCode()); } else { @@ -251,21 +282,37 @@ protected void deleteExchange(String x) throws IOException { channel.exchangeDelete(x); } + protected void deleteExchanges(String [] exchanges) throws IOException { + if (exchanges != null) { + for (String exchange : exchanges) { + deleteExchange(exchange); + } + } + } + protected void deleteQueue(String q) throws IOException { channel.queueDelete(q); } - protected void clearAllResourceAlarms() throws IOException, InterruptedException { + protected void deleteQueues(String [] queues) throws IOException { + if (queues != null) { + for (String queue : queues) { + deleteQueue(queue); + } + } + } + + protected void clearAllResourceAlarms() throws IOException { clearResourceAlarm("memory"); clearResourceAlarm("disk"); } - protected void setResourceAlarm(String source) throws IOException, InterruptedException { - Host.invokeMakeTarget("set-resource-alarm SOURCE=" + source); + protected void setResourceAlarm(String source) throws IOException { + Host.setResourceAlarm(source); } - protected void clearResourceAlarm(String source) throws IOException, InterruptedException { - Host.invokeMakeTarget("clear-resource-alarm SOURCE=" + source); + protected void clearResourceAlarm(String source) throws IOException { + Host.clearResourceAlarm(source); } protected void block() throws IOException, InterruptedException { @@ -279,10 +326,26 @@ protected void unblock() throws IOException, InterruptedException { } protected String generateQueueName() { - return "queue" + UUID.randomUUID().toString(); + return name("queue", this.testInfo.getTestClass().get(), + this.testInfo.getTestMethod().get().getName()); } protected String generateExchangeName() { - return "exchange" + UUID.randomUUID().toString(); + return name("exchange", this.testInfo.getTestClass().get(), + this.testInfo.getTestMethod().get().getName()); + } + + private static String name(String prefix, Class testClass, String testMethodName) { + String uuid = UUID.randomUUID().toString(); + return String.format( + "%s_%s_%s%s", + prefix, testClass.getSimpleName(), testMethodName, uuid.substring(uuid.length() / 2)); } + + protected boolean beforeMessageContainers() { + return versionCompare(this.brokerVersion, "3.13.0") < 0; + } + + + } diff --git a/src/test/java/com/rabbitmq/client/test/Bug20004Test.java b/src/test/java/com/rabbitmq/client/test/Bug20004Test.java new file mode 100644 index 0000000000..7c5cd8ed3a --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/Bug20004Test.java @@ -0,0 +1,69 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +/** + * Test for bug 20004 - deadlock through internal synchronization on the channel object. This is + * more properly a unit test, but since it requires a connection to a broker, it's grouped with the + * functional tests. + * + *

Test calls channel.queueDeclare, while synchronising on channel, from an independent thread. + */ +public class Bug20004Test extends BrokerTestCase { + private volatile Exception caughtException = null; + private volatile boolean created = false; + + protected void releaseResources() throws IOException { + if (created) { + channel.queueDelete("Bug20004Test"); + } + } + + @Test + public void bug20004() throws InterruptedException { + final Bug20004Test testInstance = this; + CountDownLatch completedLatch = new CountDownLatch(1); + + Thread declaringThread = + new Thread( + () -> { + try { + synchronized (channel) { + channel.queueDeclare("Bug20004Test", false, false, false, null); + testInstance.created = true; + } + } catch (Exception e) { + testInstance.caughtException = e; + } + completedLatch.countDown(); + }); + declaringThread.start(); + + boolean completed = completedLatch.await(5, TimeUnit.SECONDS); + + assertTrue(completed, "Deadlock detected?"); + assertNull(caughtException, "queueDeclare threw an exception"); + assertTrue(created, "unknown sequence of events"); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/ChannelAsyncCompletableFutureTest.java b/src/test/java/com/rabbitmq/client/test/ChannelAsyncCompletableFutureTest.java new file mode 100644 index 0000000000..1e46bb2a89 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ChannelAsyncCompletableFutureTest.java @@ -0,0 +1,114 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.impl.AMQImpl; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ChannelAsyncCompletableFutureTest extends BrokerTestCase { + + ExecutorService executor; + + String queue; + String exchange; + + @Override + protected void createResources() throws IOException, TimeoutException { + super.createResources(); + executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + queue = UUID.randomUUID().toString(); + exchange = UUID.randomUUID().toString(); + } + + @Override + protected void releaseResources() throws IOException { + super.releaseResources(); + executor.shutdownNow(); + channel.queueDelete(queue); + channel.exchangeDelete(exchange); + } + + @Test + public void async() throws Exception { + channel.confirmSelect(); + + CountDownLatch latch = new CountDownLatch(1); + AMQP.Queue.Declare queueDeclare = new AMQImpl.Queue.Declare.Builder() + .queue(queue) + .durable(true) + .exclusive(false) + .autoDelete(false) + .arguments(null) + .build(); + + channel.asyncCompletableRpc(queueDeclare) + .thenComposeAsync(action -> { + try { + return channel.asyncCompletableRpc(new AMQImpl.Exchange.Declare.Builder() + .exchange(exchange) + .type("fanout") + .durable(false) + .autoDelete(false) + .arguments(null) + .build()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, executor).thenComposeAsync(action -> { + try { + return channel.asyncCompletableRpc(new AMQImpl.Queue.Bind.Builder() + .queue(queue) + .exchange(exchange) + .routingKey("") + .arguments(null) + .build()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, executor).thenAcceptAsync(action -> { + try { + channel.basicPublish("", queue, null, "dummy".getBytes()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, executor).thenAcceptAsync((whatever) -> { + try { + channel.basicConsume(queue, true, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + latch.countDown(); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, executor); + channel.waitForConfirmsOrDie(1000); + assertTrue(latch.await(2, TimeUnit.SECONDS)); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/ChannelNTest.java b/src/test/java/com/rabbitmq/client/test/ChannelNTest.java new file mode 100644 index 0000000000..92cbf355db --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ChannelNTest.java @@ -0,0 +1,116 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Command; +import com.rabbitmq.client.Method; +import com.rabbitmq.client.TrafficListener; +import com.rabbitmq.client.impl.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Stream; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class ChannelNTest { + + ConsumerWorkService consumerWorkService; + ExecutorService executorService; + + @BeforeEach + public void init() { + executorService = Executors.newSingleThreadExecutor(); + consumerWorkService = new ConsumerWorkService(executorService, null, 1000, 1000); + } + + @AfterEach + public void tearDown() { + consumerWorkService.shutdown(); + executorService.shutdownNow(); + } + + @Test + public void serverBasicCancelForUnknownConsumerDoesNotThrowException() throws Exception { + AMQConnection connection = Mockito.mock(AMQConnection.class); + ChannelN channel = new ChannelN(connection, 1, consumerWorkService); + Method method = new AMQImpl.Basic.Cancel.Builder().consumerTag("does-not-exist").build(); + channel.processAsync(new AMQCommand(method)); + } + + @Test + public void callingBasicCancelForUnknownConsumerDoesNotThrowException() throws Exception { + AMQConnection connection = Mockito.mock(AMQConnection.class); + ChannelN channel = new ChannelN(connection, 1, consumerWorkService); + channel.basicCancel("does-not-exist"); + } + + @Test + public void qosShouldBeUnsignedShort() { + AMQConnection connection = Mockito.mock(AMQConnection.class); + ChannelN channel = new ChannelN(connection, 1, consumerWorkService); + class TestConfig { + int value; + Consumer call; + + public TestConfig(int value, Consumer call) { + this.value = value; + this.call = call; + } + } + Consumer qos = value -> channel.basicQos(value); + Consumer qosGlobal = value -> channel.basicQos(value, true); + Consumer qosPrefetchSize = value -> channel.basicQos(10, value, true); + Stream.of( + new TestConfig(-1, qos), new TestConfig(65536, qos) + ).flatMap(config -> Stream.of(config, new TestConfig(config.value, qosGlobal), new TestConfig(config.value, qosPrefetchSize))) + .forEach(config -> assertThatThrownBy(() -> config.call.apply(config.value)).isInstanceOf(IllegalArgumentException.class)); + } + + @Test + public void confirmSelectOnlySendsRPCCallOnce() throws Exception { + AMQConnection connection = Mockito.mock(AMQConnection.class); + TrafficListener trafficListener = Mockito.mock(TrafficListener.class); + + Mockito.when(connection.getTrafficListener()).thenReturn(trafficListener); + + ChannelN channel = new ChannelN(connection, 1, consumerWorkService); + + new Thread(() -> { + try { + Thread.sleep(15); + channel.handleCompleteInboundCommand(new AMQCommand(new AMQImpl.Confirm.SelectOk())); + } catch (Exception e) { + throw new RuntimeException(e); + } + }).start(); + + assertNotNull(channel.confirmSelect()); + assertNotNull(channel.confirmSelect()); + Mockito.verify(trafficListener, Mockito.times(1)).write(Mockito.any(Command.class)); + } + + interface Consumer { + + void apply(int value) throws Exception; + + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/ChannelNumberAllocationTests.java b/src/test/java/com/rabbitmq/client/test/ChannelNumberAllocationTests.java new file mode 100644 index 0000000000..9e39e86b7e --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ChannelNumberAllocationTests.java @@ -0,0 +1,128 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class ChannelNumberAllocationTests { + static final int CHANNEL_COUNT = 100; + static final Comparator COMPARATOR = new Comparator(){ + public int compare(Channel x, Channel y){ + if(x.getChannelNumber() < y.getChannelNumber()) return -1; + if(x.getChannelNumber() == y.getChannelNumber()) return 0; + return 1; + } + }; + + Connection connection; + + @BeforeEach public void setUp() throws Exception{ + connection = TestUtils.connectionFactory().newConnection(); + } + + @AfterEach public void tearDown() throws Exception{ + connection.close(); + connection = null; + } + + @Test public void allocateInOrder() throws Exception{ + for(int i = 1; i <= CHANNEL_COUNT; i++) + assertEquals(i, connection.createChannel().getChannelNumber()); + } + + @Test public void allocateAfterFreeingLast() throws Exception{ + Channel ch = connection.createChannel(); + assertEquals(1, ch.getChannelNumber()); + ch.close(); + ch = connection.createChannel(); + assertEquals(1, ch.getChannelNumber()); + } + + @Test public void allocateAfterFreeingMany() throws Exception{ + List channels = new ArrayList(); + + for(int i = 1; i <= CHANNEL_COUNT; i++) + channels.add(connection.createChannel()); + + for(Channel channel : channels){ + channel.close(); + } + + channels = new ArrayList(); + + for(int i = 1; i <= CHANNEL_COUNT; i++) + channels.add(connection.createChannel()); + + // In the current implementation the allocated numbers need not be increasing + Collections.sort(channels, COMPARATOR); + + assertEquals(CHANNEL_COUNT, channels.size(), "Didn't create the right number of channels!"); + for(int i = 1; i < CHANNEL_COUNT; ++i) { + assertTrue(channels.get(i-1).getChannelNumber() < channels.get(i).getChannelNumber(), + "Channel numbers should be distinct." + ); + } + } + + @Test public void allocateAfterManualAssign() throws Exception{ + connection.createChannel(10); + + for(int i = 0; i < 20; i++) + assertTrue(10 != connection.createChannel().getChannelNumber()); + } + + @Test public void manualAllocationDoesntBreakThings() throws Exception{ + connection.createChannel((1 << 16) - 1); + Channel ch = connection.createChannel(); + assertNotNull(ch); + } + + @Test public void reuseManuallyAllocatedChannelNumber1() throws Exception{ + connection.createChannel(1).close(); + assertNotNull(connection.createChannel(1)); + } + + @Test public void reuseManuallyAllocatedChannelNumber2() throws Exception{ + connection.createChannel(2).close(); + assertNotNull(connection.createChannel(3)); + } + + @Test public void reserveOnBoundaries() throws Exception{ + assertNotNull(connection.createChannel(3)); + assertNotNull(connection.createChannel(4)); + assertNotNull(connection.createChannel(2)); + assertNotNull(connection.createChannel(5)); + assertNotNull(connection.createChannel(1)); + } + + @Test public void channelMaxIs2047() { + int channelMaxServerSide = 2048; + int defaultChannelCount = 1; + assertEquals(channelMaxServerSide - defaultChannelCount, connection.getChannelMax()); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/ChannelRpcTimeoutIntegrationTest.java b/src/test/java/com/rabbitmq/client/test/ChannelRpcTimeoutIntegrationTest.java new file mode 100644 index 0000000000..6ee5067332 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ChannelRpcTimeoutIntegrationTest.java @@ -0,0 +1,136 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.net.SocketFactory; +import java.io.IOException; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeoutException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class ChannelRpcTimeoutIntegrationTest { + + static long waitTimeOnSomeResponses = 1000L; + + ConnectionFactory factory; + + @BeforeEach + public void setUp() { + factory = TestUtils.connectionFactory(); + } + + @AfterEach + public void tearDown() { + factory = null; + } + + @Test public void channelWaitsWhenNoTimeoutSet() throws IOException, TimeoutException { + FrameHandler frameHandler = createFrameHandler(); + ConnectionParams params = factory.params(Executors.newFixedThreadPool(1)); + WaitingAmqConnection connection = new WaitingAmqConnection(params, frameHandler); + try { + connection.start(); + Channel channel = connection.createChannel(); + channel.queueDeclare(); + } finally { + connection.close(); + } + + } + + @Test public void channelThrowsExceptionWhenTimeoutIsSet() throws IOException, TimeoutException { + FrameHandler frameHandler = createFrameHandler(); + ConnectionParams params = factory.params(Executors.newFixedThreadPool(1)); + params.setChannelRpcTimeout((int) (waitTimeOnSomeResponses / 5.0)); + WaitingAmqConnection connection = new WaitingAmqConnection(params, frameHandler); + try { + connection.start(); + Channel channel = connection.createChannel(); + try { + channel.queueDeclare(); + fail("Should time out and throw an exception"); + } catch(ChannelContinuationTimeoutException e) { + // OK + assertThat((Channel) e.getChannel()).isEqualTo(channel); + assertThat(e.getChannelNumber()).isEqualTo(channel.getChannelNumber()); + assertThat(e.getMethod()).isInstanceOf(AMQP.Queue.Declare.class); + } + } finally { + connection.close(); + } + } + + private FrameHandler createFrameHandler() throws IOException { + SocketFrameHandlerFactory socketFrameHandlerFactory = new SocketFrameHandlerFactory(ConnectionFactory.DEFAULT_CONNECTION_TIMEOUT, + SocketFactory.getDefault(), SocketConfigurators.defaultConfigurator(), false, null); + return socketFrameHandlerFactory.create(new Address("localhost"), null); + } + + static class WaitingChannel extends ChannelN { + + public WaitingChannel(AMQConnection connection, int channelNumber, ConsumerWorkService workService) { + super(connection, channelNumber, workService); + } + + @Override + public void handleCompleteInboundCommand(AMQCommand command) throws IOException { + if(command.getMethod() instanceof AMQImpl.Queue.DeclareOk) { + try { + Thread.sleep(waitTimeOnSomeResponses); + } catch (InterruptedException e) { + throw new IOException(e); + } + } + super.handleCompleteInboundCommand(command); + } + } + + static class WaitingChannelManager extends ChannelManager { + + public WaitingChannelManager(ConsumerWorkService workService, int channelMax, ThreadFactory threadFactory) { + super(workService, channelMax, threadFactory); + } + + @Override + protected ChannelN instantiateChannel(AMQConnection connection, int channelNumber, ConsumerWorkService workService) { + return new WaitingChannel(connection, channelNumber, workService); + } + } + + static class WaitingAmqConnection extends AMQConnection { + + public WaitingAmqConnection(ConnectionParams params, FrameHandler frameHandler) { + super(params, frameHandler); + } + + @Override + protected ChannelManager instantiateChannelManager(int channelMax, ThreadFactory threadFactory) { + WaitingChannelManager channelManager = new WaitingChannelManager(_workService, channelMax, threadFactory); + configureChannelManager(channelManager); + return channelManager; + } + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/ClientTestSuite.java b/src/test/java/com/rabbitmq/client/test/ClientTestSuite.java new file mode 100644 index 0000000000..6ee9091c0b --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ClientTestSuite.java @@ -0,0 +1,81 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.JacksonJsonRpcTest; +import com.rabbitmq.client.impl.*; +import com.rabbitmq.utility.IntAllocatorTests; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectClasses({ + TableTest.class, + LongStringTest.class, + BlockingCellTest.class, + TruncatedInputStreamTest.class, + AMQConnectionTest.class, + AMQChannelTest.class, + ChannelRpcTimeoutIntegrationTest.class, + ValueOrExceptionTest.class, + BrokenFramesTest.class, + ClonePropertiesTest.class, + Bug20004Test.class, + CloseInMainLoop.class, + ChannelNumberAllocationTests.class, + QueueingConsumerTests.class, + MultiThreadedChannel.class, + IntAllocatorTests.class, + AMQBuilderApiTest.class, + AmqpUriTest.class, + SharedThreadPoolTest.class, + DnsRecordIpAddressResolverTests.class, + MetricsCollectorTest.class, + MicrometerMetricsCollectorTest.class, + DnsSrvRecordAddressResolverTest.class, + JavaNioTest.class, + ConnectionFactoryTest.class, + RecoveryAwareAMQConnectionFactoryTest.class, + RpcTest.class, + LambdaCallbackTest.class, + ChannelAsyncCompletableFutureTest.class, + RecoveryDelayHandlerTest.class, + FrameBuilderTest.class, + PropertyFileInitialisationTest.class, + ClientVersionTest.class, + TestUtilsTest.class, + StrictExceptionHandlerTest.class, + NoAutoRecoveryWhenTcpWindowIsFullTest.class, + JacksonJsonRpcTest.class, + AddressTest.class, + DefaultRetryHandlerTest.class, + NioDeadlockOnConnectionClosing.class, + GeneratedClassesTest.class, + RpcTopologyRecordingTest.class, + ConnectionTest.class, + TlsUtilsTest.class, + ChannelNTest.class, + RefreshProtectedCredentialsProviderTest.class, + DefaultCredentialsRefreshServiceTest.class, + OAuth2ClientCredentialsGrantCredentialsProviderTest.class, + RefreshCredentialsTest.class, + AMQConnectionRefreshCredentialsTest.class, + ValueWriterTest.class, + BlockedConnectionTest.class +}) +public class ClientTestSuite { + +} diff --git a/src/test/java/com/rabbitmq/client/test/ClientVersionTest.java b/src/test/java/com/rabbitmq/client/test/ClientVersionTest.java new file mode 100644 index 0000000000..d782e050cf --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ClientVersionTest.java @@ -0,0 +1,30 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.impl.ClientVersion; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ClientVersionTest { + + @Test + public void clientVersion() { + assertThat(ClientVersion.VERSION).isNotNull(); + assertThat(ClientVersion.VERSION).isNotEqualTo("0.0.0"); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/ClonePropertiesTest.java b/src/test/java/com/rabbitmq/client/test/ClonePropertiesTest.java new file mode 100644 index 0000000000..330354308c --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ClonePropertiesTest.java @@ -0,0 +1,45 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.AMQP.BasicProperties; +import com.rabbitmq.client.MessageProperties; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ClonePropertiesTest { + + + @Test public void propertyCloneIsDistinct() + throws CloneNotSupportedException + { + assertTrue(MessageProperties.MINIMAL_PERSISTENT_BASIC != + MessageProperties.MINIMAL_PERSISTENT_BASIC.clone()); + } + + @Test public void propertyClonePreservesValues() + throws CloneNotSupportedException + { + assertEquals(MessageProperties.MINIMAL_PERSISTENT_BASIC.getDeliveryMode(), + ((BasicProperties) MessageProperties.MINIMAL_PERSISTENT_BASIC.clone()) + .getDeliveryMode()); + assertEquals(Integer.valueOf(2), + ((BasicProperties) MessageProperties.MINIMAL_PERSISTENT_BASIC.clone()) + .getDeliveryMode()); + } +} diff --git a/test/src/com/rabbitmq/client/test/CloseInMainLoop.java b/src/test/java/com/rabbitmq/client/test/CloseInMainLoop.java similarity index 78% rename from test/src/com/rabbitmq/client/test/CloseInMainLoop.java rename to src/test/java/com/rabbitmq/client/test/CloseInMainLoop.java index c94fc488d5..c7787de5ce 100644 --- a/test/src/com/rabbitmq/client/test/CloseInMainLoop.java +++ b/src/test/java/com/rabbitmq/client/test/CloseInMainLoop.java @@ -1,21 +1,22 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; @@ -24,6 +25,8 @@ import javax.net.SocketFactory; +import org.junit.jupiter.api.Test; + import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Command; @@ -39,7 +42,7 @@ public class CloseInMainLoop extends BrokerTestCase{ private final CountDownLatch closeLatch = new CountDownLatch(1); private ConnectionFactory specialConnectionFactory() { - ConnectionFactory f = new ConnectionFactory(); + ConnectionFactory f = TestUtils.connectionFactory(); f.setExceptionHandler(new DefaultExceptionHandler(){ @Override public void handleConsumerException(Channel channel, @@ -88,7 +91,7 @@ public boolean processControlCommand(Command c) throws IOException{ } } - public void testCloseOKNormallyReceived() throws Exception{ + @Test public void closeOKNormallyReceived() throws Exception{ SpecialConnection connection = new SpecialConnection(); connection.close(); assertTrue(connection.hadValidShutdown()); @@ -96,7 +99,7 @@ public void testCloseOKNormallyReceived() throws Exception{ // The thrown runtime exception should get intercepted by the // consumer exception handler, and result in a clean shut down. - public void testCloseWithFaultyConsumer() throws Exception{ + @Test public void closeWithFaultyConsumer() throws Exception{ SpecialConnection connection = new SpecialConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare("x", "direct"); diff --git a/test/src/com/rabbitmq/client/test/ConfirmBase.java b/src/test/java/com/rabbitmq/client/test/ConfirmBase.java similarity index 65% rename from test/src/com/rabbitmq/client/test/ConfirmBase.java rename to src/test/java/com/rabbitmq/client/test/ConfirmBase.java index 576005aeb3..283d233ce8 100644 --- a/test/src/com/rabbitmq/client/test/ConfirmBase.java +++ b/src/test/java/com/rabbitmq/client/test/ConfirmBase.java @@ -1,31 +1,32 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test; -import com.rabbitmq.client.ShutdownSignalException; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.ShutdownSignalException; +import org.opentest4j.AssertionFailedError; -import junit.framework.AssertionFailedError; public class ConfirmBase extends BrokerTestCase { protected void waitForConfirms() throws Exception diff --git a/src/test/java/com/rabbitmq/client/test/ConnectionFactoryTest.java b/src/test/java/com/rabbitmq/client/test/ConnectionFactoryTest.java new file mode 100644 index 0000000000..978d57e9fe --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ConnectionFactoryTest.java @@ -0,0 +1,313 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.*; +import com.rabbitmq.client.impl.nio.NioParams; +import com.rabbitmq.client.impl.recovery.RecoveredQueueNameSupplier; +import com.rabbitmq.client.impl.recovery.RetryHandler; +import com.rabbitmq.client.impl.recovery.TopologyRecoveryFilter; +import org.junit.jupiter.api.Test; + +import javax.net.SocketFactory; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.*; + +public class ConnectionFactoryTest { + + // see https://github.com/rabbitmq/rabbitmq-java-client/issues/262 + @Test + public void tryNextAddressIfTimeoutExceptionNoAutoRecovery() throws IOException, TimeoutException { + final AMQConnection connectionThatThrowsTimeout = mock(AMQConnection.class); + final AMQConnection connectionThatSucceeds = mock(AMQConnection.class); + final Queue connections = new ArrayBlockingQueue(10); + connections.add(connectionThatThrowsTimeout); + connections.add(connectionThatSucceeds); + ConnectionFactory connectionFactory = new ConnectionFactory() { + + @Override + protected AMQConnection createConnection(ConnectionParams params, FrameHandler frameHandler, MetricsCollector metricsCollector) { + return connections.poll(); + } + + @Override + protected synchronized FrameHandlerFactory createFrameHandlerFactory() { + return mock(FrameHandlerFactory.class); + } + }; + connectionFactory.setAutomaticRecoveryEnabled(false); + doThrow(TimeoutException.class).when(connectionThatThrowsTimeout).start(); + doNothing().when(connectionThatSucceeds).start(); + Connection returnedConnection = connectionFactory.newConnection( + new Address[]{new Address("host1"), new Address("host2")} + ); + assertThat(returnedConnection).isSameAs(connectionThatSucceeds); + } + + // see https://github.com/rabbitmq/rabbitmq-java-client/pull/350 + @Test + public void customizeCredentialsProvider() throws Exception { + final CredentialsProvider provider = mock(CredentialsProvider.class); + final AMQConnection connection = mock(AMQConnection.class); + final AtomicBoolean createCalled = new AtomicBoolean(false); + + ConnectionFactory connectionFactory = new ConnectionFactory() { + @Override + protected AMQConnection createConnection(ConnectionParams params, FrameHandler frameHandler, + MetricsCollector metricsCollector) { + assertThat(provider).isSameAs(params.getCredentialsProvider()); + createCalled.set(true); + return connection; + } + + @Override + protected synchronized FrameHandlerFactory createFrameHandlerFactory() { + return mock(FrameHandlerFactory.class); + } + }; + connectionFactory.setCredentialsProvider(provider); + connectionFactory.setAutomaticRecoveryEnabled(false); + + doNothing().when(connection).start(); + + Connection returnedConnection = connectionFactory.newConnection(); + assertThat(returnedConnection).isSameAs(connection); + assertThat(createCalled).isTrue(); + } + + @Test + public void shouldUseDnsResolutionWhenOneAddressAndNoTls() throws Exception { + AMQConnection connection = mock(AMQConnection.class); + AtomicReference addressResolver = new AtomicReference<>(); + + ConnectionFactory connectionFactory = new ConnectionFactory() { + @Override + protected AMQConnection createConnection(ConnectionParams params, FrameHandler frameHandler, + MetricsCollector metricsCollector) { + return connection; + } + + @Override + protected AddressResolver createAddressResolver(List

addresses) { + addressResolver.set(super.createAddressResolver(addresses)); + return addressResolver.get(); + } + + @Override + protected synchronized FrameHandlerFactory createFrameHandlerFactory() { + return mock(FrameHandlerFactory.class); + } + }; + // connection recovery makes the creation path more complex + connectionFactory.setAutomaticRecoveryEnabled(false); + + doNothing().when(connection).start(); + connectionFactory.newConnection(); + assertThat(addressResolver.get()).isNotNull().isInstanceOf(DnsRecordIpAddressResolver.class); + } + + @Test + public void shouldUseDnsResolutionWhenOneAddressAndTls() throws Exception { + AMQConnection connection = mock(AMQConnection.class); + AtomicReference addressResolver = new AtomicReference<>(); + + ConnectionFactory connectionFactory = new ConnectionFactory() { + @Override + protected AMQConnection createConnection(ConnectionParams params, FrameHandler frameHandler, + MetricsCollector metricsCollector) { + return connection; + } + + @Override + protected AddressResolver createAddressResolver(List
addresses) { + addressResolver.set(super.createAddressResolver(addresses)); + return addressResolver.get(); + } + + @Override + protected synchronized FrameHandlerFactory createFrameHandlerFactory() { + return mock(FrameHandlerFactory.class); + } + }; + // connection recovery makes the creation path more complex + connectionFactory.setAutomaticRecoveryEnabled(false); + + doNothing().when(connection).start(); + connectionFactory.useSslProtocol(); + connectionFactory.newConnection(); + + assertThat(addressResolver.get()).isNotNull().isInstanceOf(DnsRecordIpAddressResolver.class); + } + + @Test + public void heartbeatAndChannelMaxMustBeUnsignedShorts() { + class TestConfig { + int value; + Consumer call; + boolean expectException; + + public TestConfig(int value, Consumer call, boolean expectException) { + this.value = value; + this.call = call; + this.expectException = expectException; + } + } + + ConnectionFactory cf = new ConnectionFactory(); + Consumer setHeartbeat = cf::setRequestedHeartbeat; + Consumer setChannelMax = cf::setRequestedChannelMax; + + Stream.of( + new TestConfig(0, setHeartbeat, false), + new TestConfig(10, setHeartbeat, false), + new TestConfig(65535, setHeartbeat, false), + new TestConfig(-1, setHeartbeat, true), + new TestConfig(65536, setHeartbeat, true)) + .flatMap(config -> Stream.of(config, new TestConfig(config.value, setChannelMax, config.expectException))) + .forEach(config -> { + if (config.expectException) { + assertThatThrownBy(() -> config.call.accept(config.value)).isInstanceOf(IllegalArgumentException.class); + } else { + config.call.accept(config.value); + } + }); + + } + + @Test + public void shouldBeConfigurableUsingFluentAPI() throws Exception { + /* GIVEN */ + Map clientProperties = new HashMap<>(); + SaslConfig saslConfig = mock(SaslConfig.class); + ConnectionFactory connectionFactory = new ConnectionFactory(); + SocketFactory socketFactory = mock(SocketFactory.class); + SocketConfigurator socketConfigurator = mock(SocketConfigurator.class); + ExecutorService executorService = mock(ExecutorService.class); + ScheduledExecutorService scheduledExecutorService = mock(ScheduledExecutorService.class); + ThreadFactory threadFactory = mock(ThreadFactory.class); + ExceptionHandler exceptionHandler = mock(ExceptionHandler.class); + MetricsCollector metricsCollector = mock(MetricsCollector.class); + CredentialsRefreshService credentialsRefreshService = mock(CredentialsRefreshService.class); + RecoveryDelayHandler recoveryDelayHandler = mock(RecoveryDelayHandler.class); + NioParams nioParams = mock(NioParams.class); + SslContextFactory sslContextFactory = mock(SslContextFactory.class); + TopologyRecoveryFilter topologyRecoveryFilter = mock(TopologyRecoveryFilter.class); + Predicate connectionRecoveryTriggeringCondition = (ShutdownSignalException) -> true; + RetryHandler retryHandler = mock(RetryHandler.class); + RecoveredQueueNameSupplier recoveredQueueNameSupplier = mock(RecoveredQueueNameSupplier.class); + + /* WHEN */ + connectionFactory + .setHost("rabbitmq") + .setPort(5672) + .setUsername("guest") + .setPassword("guest") + .setVirtualHost("/") + .setRequestedChannelMax(1) + .setRequestedFrameMax(2) + .setRequestedHeartbeat(3) + .setConnectionTimeout(4) + .setHandshakeTimeout(5) + .setShutdownTimeout(6) + .setClientProperties(clientProperties) + .setSaslConfig(saslConfig) + .setSocketFactory(socketFactory) + .setSocketConfigurator(socketConfigurator) + .setSharedExecutor(executorService) + .setShutdownExecutor(executorService) + .setHeartbeatExecutor(scheduledExecutorService) + .setThreadFactory(threadFactory) + .setExceptionHandler(exceptionHandler) + .setAutomaticRecoveryEnabled(true) + .setTopologyRecoveryEnabled(true) + .setTopologyRecoveryExecutor(executorService) + .setMetricsCollector(metricsCollector) + .setCredentialsRefreshService(credentialsRefreshService) + .setNetworkRecoveryInterval(7) + .setRecoveryDelayHandler(recoveryDelayHandler) + .setNioParams(nioParams) + .useNio() + .useBlockingIo() + .setChannelRpcTimeout(8) + .setSslContextFactory(sslContextFactory) + .setChannelShouldCheckRpcResponseType(true) + .setWorkPoolTimeout(9) + .setTopologyRecoveryFilter(topologyRecoveryFilter) + .setConnectionRecoveryTriggeringCondition(connectionRecoveryTriggeringCondition) + .setTopologyRecoveryRetryHandler(retryHandler) + .setRecoveredQueueNameSupplier(recoveredQueueNameSupplier); + + /* THEN */ + assertThat(connectionFactory.getHost()).isEqualTo("rabbitmq"); + assertThat(connectionFactory.getPort()).isEqualTo(5672); + assertThat(connectionFactory.getUsername()).isEqualTo("guest"); + assertThat(connectionFactory.getPassword()).isEqualTo("guest"); + assertThat(connectionFactory.getVirtualHost()).isEqualTo("/"); + assertThat(connectionFactory.getRequestedChannelMax()).isEqualTo(1); + assertThat(connectionFactory.getRequestedFrameMax()).isEqualTo(2); + assertThat(connectionFactory.getRequestedHeartbeat()).isEqualTo(3); + assertThat(connectionFactory.getConnectionTimeout()).isEqualTo(4); + assertThat(connectionFactory.getHandshakeTimeout()).isEqualTo(5); + assertThat(connectionFactory.getShutdownTimeout()).isEqualTo(6); + assertThat(connectionFactory.getClientProperties()).isEqualTo(clientProperties); + assertThat(connectionFactory.getSaslConfig()).isEqualTo(saslConfig); + assertThat(connectionFactory.getSocketFactory()).isEqualTo(socketFactory); + assertThat(connectionFactory.getSocketConfigurator()).isEqualTo(socketConfigurator); + assertThat(connectionFactory.isAutomaticRecoveryEnabled()).isEqualTo(true); + assertThat(connectionFactory.isTopologyRecoveryEnabled()).isEqualTo(true); + assertThat(connectionFactory.getMetricsCollector()).isEqualTo(metricsCollector); + assertThat(connectionFactory.getNetworkRecoveryInterval()).isEqualTo(7); + assertThat(connectionFactory.getRecoveryDelayHandler()).isEqualTo(recoveryDelayHandler); + assertThat(connectionFactory.getNioParams()).isEqualTo(nioParams); + assertThat(connectionFactory.getChannelRpcTimeout()).isEqualTo(8); + assertThat(connectionFactory.isChannelShouldCheckRpcResponseType()).isEqualTo(true); + assertThat(connectionFactory.getWorkPoolTimeout()).isEqualTo(9); + assertThat(connectionFactory.isSSL()).isEqualTo(true); + + /* Now test cross-cutting setters that override properties set by other setters */ + CredentialsProvider credentialsProvider = mock(CredentialsProvider.class); + when(credentialsProvider.getUsername()).thenReturn("admin"); + when(credentialsProvider.getPassword()).thenReturn("admin"); + connectionFactory + .setCredentialsProvider(credentialsProvider) + .setUri("amqp://host:5671") + .useSslProtocol("TLSv1.2"); + assertThat(connectionFactory.getHost()).isEqualTo("host"); + assertThat(connectionFactory.getPort()).isEqualTo(5671); + assertThat(connectionFactory.getUsername()).isEqualTo("admin"); + assertThat(connectionFactory.getPassword()).isEqualTo("admin"); + assertThat(connectionFactory.isSSL()).isEqualTo(true); + } + + @Test + void newConnectionWithEmptyAddressListShouldThrowException() { + ConnectionFactory cf = new ConnectionFactory(); + assertThatThrownBy(() -> cf.newConnection(Collections.emptyList())); + assertThatThrownBy(() -> cf.newConnection(new Address[] {})); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/ConnectionTest.java b/src/test/java/com/rabbitmq/client/test/ConnectionTest.java new file mode 100644 index 0000000000..7681116e09 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ConnectionTest.java @@ -0,0 +1,141 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.stubbing.OngoingStubbing; + +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +public class ConnectionTest { + + @Mock + MyConnection c = mock(MyConnection.class); + @Mock + Channel ch = mock(Channel.class); + + AutoCloseable mocks; + + public static Object[] configurators() { + return new Object[]{new NotNumberedChannelCreationCallback(), new NumberedChannelCreationCallback()}; + } + + @BeforeEach + public void init() { + mocks = openMocks(this); + } + + @AfterEach + public void tearDown() throws Exception { + mocks.close(); + } + + @ParameterizedTest + @MethodSource("configurators") + public void openChannelWithNonNullChannelShouldReturnNonEmptyOptional(TestConfigurator configurator) throws Exception { + configurator.mockAndWhenChannel(c).thenReturn(ch); + configurator.mockAndWhenOptional(c).thenCallRealMethod(); + Optional optional = configurator.open(c); + assertTrue(optional.isPresent()); + assertSame(ch, optional.get()); + } + + @ParameterizedTest + @MethodSource("configurators") + public void openChannelWithNullChannelShouldReturnEmptyOptional(TestConfigurator configurator) throws Exception { + configurator.mockAndWhenChannel(c).thenReturn(null); + configurator.mockAndWhenOptional(c).thenCallRealMethod(); + Assertions.assertThatThrownBy(() -> { + Optional optional = configurator.open(c); + assertFalse(optional.isPresent()); + optional.get(); + }).isInstanceOf(NoSuchElementException.class); + } + + @ParameterizedTest + @MethodSource("configurators") + public void openChannelShouldPropagateIoException(TestConfigurator configurator) throws Exception { + configurator.mockAndWhenChannel(c).thenThrow(IOException.class); + configurator.mockAndWhenOptional(c).thenCallRealMethod(); + Assertions.assertThatThrownBy(() -> configurator.open(c)).isInstanceOf(IOException.class); + } + + interface TestConfigurator { + + OngoingStubbing mockAndWhenChannel(Connection c) throws IOException; + + OngoingStubbing> mockAndWhenOptional(Connection c) throws IOException; + + Optional open(Connection c) throws IOException; + + } + + static class NotNumberedChannelCreationCallback implements TestConfigurator { + + @Override + public OngoingStubbing mockAndWhenChannel(Connection c) throws IOException { + return when(c.createChannel()); + } + + @Override + public OngoingStubbing> mockAndWhenOptional(Connection c) throws IOException { + return when(c.openChannel()); + } + + @Override + public Optional open(Connection c) throws IOException { + return c.openChannel(); + } + } + + static class NumberedChannelCreationCallback implements TestConfigurator { + + @Override + public OngoingStubbing mockAndWhenChannel(Connection c) throws IOException { + return when(c.createChannel(1)); + } + + @Override + public OngoingStubbing> mockAndWhenOptional(Connection c) throws IOException { + return when(c.openChannel(1)); + } + + @Override + public Optional open(Connection c) throws IOException { + return c.openChannel(1); + } + } + + // trick to make Mockito call the optional method defined in the interface + static abstract class MyConnection implements Connection { + + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/DefaultRetryHandlerTest.java b/src/test/java/com/rabbitmq/client/test/DefaultRetryHandlerTest.java new file mode 100644 index 0000000000..ca3f40a8b3 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/DefaultRetryHandlerTest.java @@ -0,0 +1,265 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.impl.recovery.BackoffPolicy; +import com.rabbitmq.client.impl.recovery.DefaultRetryHandler; +import com.rabbitmq.client.impl.recovery.RecordedBinding; +import com.rabbitmq.client.impl.recovery.RecordedConsumer; +import com.rabbitmq.client.impl.recovery.RecordedExchange; +import com.rabbitmq.client.impl.recovery.RecordedQueue; +import com.rabbitmq.client.impl.recovery.RetryContext; +import com.rabbitmq.client.impl.recovery.RetryHandler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.verification.VerificationMode; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiPredicate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.intThat; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +/** + * + */ +public class DefaultRetryHandlerTest { + + RetryHandler handler; + + @Mock + BiPredicate queueRecoveryRetryCondition; + @Mock + BiPredicate exchangeRecoveryRetryCondition; + @Mock + BiPredicate bindingRecoveryRetryCondition; + @Mock + BiPredicate consumerRecoveryRetryCondition; + + @Mock + DefaultRetryHandler.RetryOperation queueRecoveryRetryOperation; + @Mock + DefaultRetryHandler.RetryOperation exchangeRecoveryRetryOperation; + @Mock + DefaultRetryHandler.RetryOperation bindingRecoveryRetryOperation; + @Mock + DefaultRetryHandler.RetryOperation consumerRecoveryRetryOperation; + + @Mock + BackoffPolicy backoffPolicy; + + AutoCloseable mocks; + + @BeforeEach + public void init() { + mocks = openMocks(this); + } + + @AfterEach + public void tearDown() throws Exception { + mocks.close(); + } + + @Test + public void shouldNotRetryWhenConditionReturnsFalse() throws Exception { + conditionsReturn(false); + handler = handler(); + assertExceptionIsThrown( + "No retry, initial exception should have been re-thrown", + () -> handler.retryQueueRecovery(retryContext()) + ); + assertExceptionIsThrown( + "No retry, initial exception should have been re-thrown", + () -> handler.retryExchangeRecovery(retryContext()) + ); + assertExceptionIsThrown( + "No retry, initial exception should have been re-thrown", + () -> handler.retryBindingRecovery(retryContext()) + ); + assertExceptionIsThrown( + "No retry, initial exception should have been re-thrown", + () -> handler.retryConsumerRecovery(retryContext()) + ); + verifyConditionsInvocation(times(1)); + verifyOperationsInvocation(never()); + verify(backoffPolicy, never()).backoff(anyInt()); + } + + @Test + public void shouldReturnOperationResultInRetryResultWhenRetrying() throws Exception { + conditionsReturn(true); + when(queueRecoveryRetryOperation.call(any(RetryContext.class))).thenReturn("queue"); + when(exchangeRecoveryRetryOperation.call(any(RetryContext.class))).thenReturn("exchange"); + when(bindingRecoveryRetryOperation.call(any(RetryContext.class))).thenReturn("binding"); + when(consumerRecoveryRetryOperation.call(any(RetryContext.class))).thenReturn("consumer"); + handler = handler(); + assertEquals( + "queue", + handler.retryQueueRecovery(retryContext()).getResult() + ); + assertEquals( + "exchange", + handler.retryExchangeRecovery(retryContext()).getResult() + ); + assertEquals( + "binding", + handler.retryBindingRecovery(retryContext()).getResult() + ); + assertEquals( + "consumer", + handler.retryConsumerRecovery(retryContext()).getResult() + ); + verifyConditionsInvocation(times(1)); + verifyOperationsInvocation(times(1)); + verify(backoffPolicy, times(1 * 4)).backoff(1); + } + + @Test + public void shouldRetryWhenOperationFailsAndConditionIsTrue() throws Exception { + conditionsReturn(true); + when(queueRecoveryRetryOperation.call(any(RetryContext.class))) + .thenThrow(new Exception()).thenReturn("queue"); + when(exchangeRecoveryRetryOperation.call(any(RetryContext.class))) + .thenThrow(new Exception()).thenReturn("exchange"); + when(bindingRecoveryRetryOperation.call(any(RetryContext.class))) + .thenThrow(new Exception()).thenReturn("binding"); + when(consumerRecoveryRetryOperation.call(any(RetryContext.class))) + .thenThrow(new Exception()).thenReturn("consumer"); + handler = handler(2); + assertEquals( + "queue", + handler.retryQueueRecovery(retryContext()).getResult() + ); + assertEquals( + "exchange", + handler.retryExchangeRecovery(retryContext()).getResult() + ); + assertEquals( + "binding", + handler.retryBindingRecovery(retryContext()).getResult() + ); + assertEquals( + "consumer", + handler.retryConsumerRecovery(retryContext()).getResult() + ); + verifyConditionsInvocation(times(2)); + verifyOperationsInvocation(times(2)); + checkBackoffSequence(1, 2, 1, 2, 1, 2, 1, 2); + } + + @Test + public void shouldThrowExceptionWhenRetryAttemptsIsExceeded() throws Exception { + conditionsReturn(true); + when(queueRecoveryRetryOperation.call(any(RetryContext.class))) + .thenThrow(new Exception()); + when(exchangeRecoveryRetryOperation.call(any(RetryContext.class))) + .thenThrow(new Exception()); + when(bindingRecoveryRetryOperation.call(any(RetryContext.class))) + .thenThrow(new Exception()); + when(consumerRecoveryRetryOperation.call(any(RetryContext.class))) + .thenThrow(new Exception()); + handler = handler(3); + assertExceptionIsThrown( + "Retry exhausted, an exception should have been thrown", + () -> handler.retryQueueRecovery(retryContext()) + ); + assertExceptionIsThrown( + "Retry exhausted, an exception should have been thrown", + () -> handler.retryExchangeRecovery(retryContext()) + ); + assertExceptionIsThrown( + "Retry exhausted, an exception should have been thrown", + () -> handler.retryBindingRecovery(retryContext()) + ); + assertExceptionIsThrown( + "Retry exhausted, an exception should have been thrown", + () -> handler.retryConsumerRecovery(retryContext()) + ); + verifyConditionsInvocation(times(3)); + verifyOperationsInvocation(times(3)); + checkBackoffSequence(1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3); + } + + private void assertExceptionIsThrown(String message, Callable action) { + try { + action.call(); + fail(message); + } catch (Exception e) { + } + } + + private void conditionsReturn(boolean shouldRetry) { + when(queueRecoveryRetryCondition.test(nullable(RecordedQueue.class), nullable(Exception.class))) + .thenReturn(shouldRetry); + when(exchangeRecoveryRetryCondition.test(nullable(RecordedExchange.class), nullable(Exception.class))) + .thenReturn(shouldRetry); + when(bindingRecoveryRetryCondition.test(nullable(RecordedBinding.class), nullable(Exception.class))) + .thenReturn(shouldRetry); + when(consumerRecoveryRetryCondition.test(nullable(RecordedConsumer.class), nullable(Exception.class))) + .thenReturn(shouldRetry); + } + + private void verifyConditionsInvocation(VerificationMode mode) { + verify(queueRecoveryRetryCondition, mode).test(nullable(RecordedQueue.class), any(Exception.class)); + verify(exchangeRecoveryRetryCondition, mode).test(nullable(RecordedExchange.class), any(Exception.class)); + verify(bindingRecoveryRetryCondition, mode).test(nullable(RecordedBinding.class), any(Exception.class)); + verify(consumerRecoveryRetryCondition, mode).test(nullable(RecordedConsumer.class), any(Exception.class)); + } + + private void verifyOperationsInvocation(VerificationMode mode) throws Exception { + verify(queueRecoveryRetryOperation, mode).call(any(RetryContext.class)); + verify(exchangeRecoveryRetryOperation, mode).call(any(RetryContext.class)); + verify(bindingRecoveryRetryOperation, mode).call(any(RetryContext.class)); + verify(consumerRecoveryRetryOperation, mode).call(any(RetryContext.class)); + } + + private RetryHandler handler() { + return handler(1); + } + + private RetryHandler handler(int retryAttempts) { + return new DefaultRetryHandler( + queueRecoveryRetryCondition, exchangeRecoveryRetryCondition, + bindingRecoveryRetryCondition, consumerRecoveryRetryCondition, + queueRecoveryRetryOperation, exchangeRecoveryRetryOperation, + bindingRecoveryRetryOperation, consumerRecoveryRetryOperation, + retryAttempts, + backoffPolicy); + } + + private RetryContext retryContext() { + return new RetryContext(null, new Exception(), null); + } + + private void checkBackoffSequence(int... sequence) throws InterruptedException { + AtomicInteger count = new AtomicInteger(0); + verify(backoffPolicy, times(sequence.length)) + // for some reason Mockito calls the matchers twice as many times as the target method + .backoff(intThat(i -> i == sequence[count.getAndIncrement() % sequence.length])); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/DnsRecordIpAddressResolverTests.java b/src/test/java/com/rabbitmq/client/test/DnsRecordIpAddressResolverTests.java new file mode 100644 index 0000000000..c339bf34e8 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/DnsRecordIpAddressResolverTests.java @@ -0,0 +1,52 @@ +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DnsRecordIpAddressResolver; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * + */ +public class DnsRecordIpAddressResolverTests extends BrokerTestCase { + + @Test public void localhostResolution() throws IOException, TimeoutException { + DnsRecordIpAddressResolver addressResolver = new DnsRecordIpAddressResolver("localhost"); + ConnectionFactory connectionFactory = newConnectionFactory(); + Connection connection = connectionFactory.newConnection(addressResolver); + try { + connection.createChannel(); + } finally { + connection.abort(); + } + } + + @Test public void loopbackInterfaceResolution() throws IOException, TimeoutException { + DnsRecordIpAddressResolver addressResolver = new DnsRecordIpAddressResolver("127.0.0.1"); + ConnectionFactory connectionFactory = newConnectionFactory(); + Connection connection = connectionFactory.newConnection(addressResolver); + try { + connection.createChannel(); + } finally { + connection.abort(); + } + } + + @Test public void resolutionFails() throws IOException, TimeoutException { + DnsRecordIpAddressResolver addressResolver = new DnsRecordIpAddressResolver( + "afancyandunlikelyhostname" + ); + try { + connectionFactory.newConnection(addressResolver); + fail("The host resolution should have failed"); + } catch (UnknownHostException e) { + // expected + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/DnsSrvRecordAddressResolverTest.java b/src/test/java/com/rabbitmq/client/test/DnsSrvRecordAddressResolverTest.java new file mode 100644 index 0000000000..c121db83da --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/DnsSrvRecordAddressResolverTest.java @@ -0,0 +1,56 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Address; +import com.rabbitmq.client.DnsSrvRecordAddressResolver; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + */ +public class DnsSrvRecordAddressResolverTest { + + @Test public void recordsParsedAndSorted() throws IOException { + DnsSrvRecordAddressResolver resolver = new DnsSrvRecordAddressResolver("rabbitmq") { + @Override + protected List lookupSrvRecords(String service, String dnsUrls) throws IOException { + return Arrays.asList( + DnsSrvRecordAddressResolver.SrvRecord.fromSrvQueryResult("20 0 5269 alt2.xmpp-server.l.google.com."), + DnsSrvRecordAddressResolver.SrvRecord.fromSrvQueryResult("30 0 5269 alt3.xmpp-server.l.google.com."), + DnsSrvRecordAddressResolver.SrvRecord.fromSrvQueryResult("10 0 5269 alt1.xmpp-server.l.google.com."), + DnsSrvRecordAddressResolver.SrvRecord.fromSrvQueryResult("50 0 5269 alt5.xmpp-server.l.google.com."), + DnsSrvRecordAddressResolver.SrvRecord.fromSrvQueryResult("40 0 5269 alt4.xmpp-server.l.google.com.") + ); + } + }; + + List
addresses = resolver.getAddresses(); + assertThat(addresses.size()).isEqualTo(5); + assertThat(addresses.get(0).getHost()).isEqualTo("alt1.xmpp-server.l.google.com"); + assertThat(addresses.get(1).getHost()).isEqualTo("alt2.xmpp-server.l.google.com"); + assertThat(addresses.get(2).getHost()).isEqualTo("alt3.xmpp-server.l.google.com"); + assertThat(addresses.get(3).getHost()).isEqualTo("alt4.xmpp-server.l.google.com"); + assertThat(addresses.get(4).getHost()).isEqualTo("alt5.xmpp-server.l.google.com"); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/FrameBuilderTest.java b/src/test/java/com/rabbitmq/client/test/FrameBuilderTest.java new file mode 100644 index 0000000000..e5ecbdc324 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/FrameBuilderTest.java @@ -0,0 +1,151 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.MalformedFrameException; +import com.rabbitmq.client.impl.Frame; +import com.rabbitmq.client.impl.nio.FrameBuilder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +/** + * + */ +public class FrameBuilderTest { + + @Mock + ReadableByteChannel channel; + + ByteBuffer buffer; + + FrameBuilder builder; + + AutoCloseable mocks; + + @BeforeEach + void init() { + this.mocks = MockitoAnnotations.openMocks(this); + } + + @AfterEach + void tearDown() throws Exception { + mocks.close(); + } + + @Test + public void buildFrameInOneGo() throws IOException { + buffer = ByteBuffer.wrap(new byte[] { 1, 0, 0, 0, 0, 0, 3, 1, 2, 3, end() }); + builder = new FrameBuilder(channel, buffer, Integer.MAX_VALUE); + Frame frame = builder.readFrame(); + assertThat(frame).isNotNull(); + assertThat(frame.getType()).isEqualTo(1); + assertThat(frame.getChannel()).isEqualTo(0); + assertThat(frame.getPayload()).hasSize(3); + } + + @Test + public void buildFramesInOneGo() throws IOException { + byte[] frameContent = new byte[] { 1, 0, 0, 0, 0, 0, 3, 1, 2, 3, end() }; + int nbFrames = 13; + byte[] frames = new byte[frameContent.length * nbFrames]; + for (int i = 0; i < nbFrames; i++) { + for (int j = 0; j < frameContent.length; j++) { + frames[i * frameContent.length + j] = frameContent[j]; + } + } + buffer = ByteBuffer.wrap(frames); + builder = new FrameBuilder(channel, buffer, Integer.MAX_VALUE); + int frameCount = 0; + Frame frame; + while ((frame = builder.readFrame()) != null) { + assertThat(frame).isNotNull(); + assertThat(frame.getType()).isEqualTo(1); + assertThat(frame.getChannel()).isEqualTo(0); + assertThat(frame.getPayload()).hasSize(3); + frameCount++; + } + assertThat(frameCount).isEqualTo(nbFrames); + } + + @Test + public void buildFrameInSeveralCalls() throws IOException { + buffer = ByteBuffer.wrap(new byte[] { 1, 0, 0, 0, 0, 0, 3, 1, 2 }); + builder = new FrameBuilder(channel, buffer, Integer.MAX_VALUE); + Frame frame = builder.readFrame(); + assertThat(frame).isNull(); + + buffer.clear(); + buffer.put(b(3)).put(end()); + buffer.flip(); + + frame = builder.readFrame(); + assertThat(frame).isNotNull(); + assertThat(frame.getType()).isEqualTo(1); + assertThat(frame.getChannel()).isEqualTo(0); + assertThat(frame.getPayload()).hasSize(3); + } + + @Test + public void protocolMismatchHeader() throws IOException { + ByteBuffer[] buffers = new ByteBuffer[] { + ByteBuffer.wrap(new byte[] { 'A' }), + ByteBuffer.wrap(new byte[] { 'A', 'M', 'Q' }), + ByteBuffer.wrap(new byte[] { 'A', 'N', 'Q', 'P' }), + ByteBuffer.wrap(new byte[] { 'A', 'M', 'Q', 'P' }), + ByteBuffer.wrap(new byte[] { 'A', 'M', 'Q', 'P', 1, 1, 8 }), + ByteBuffer.wrap(new byte[] { 'A', 'M', 'Q', 'P', 1, 1, 8, 0 }), + ByteBuffer.wrap(new byte[] { 'A', 'M', 'Q', 'P', 1, 1, 9, 1 }) + }; + String[] messages = new String[] { + "Invalid AMQP protocol header from server: read only 1 byte(s) instead of 4", + "Invalid AMQP protocol header from server: read only 3 byte(s) instead of 4", + "Invalid AMQP protocol header from server: expected character 77, got 78", + "Invalid AMQP protocol header from server", + "Invalid AMQP protocol header from server", + "AMQP protocol version mismatch; we are version 0-9-1, server is 0-8", + "AMQP protocol version mismatch; we are version 0-9-1, server sent signature 1,1,9,1" + }; + + for (int i = 0; i < buffers.length; i++) { + builder = new FrameBuilder(channel, buffers[i], Integer.MAX_VALUE); + try { + builder.readFrame(); + fail("protocol header not correct, exception should have been thrown"); + } catch (MalformedFrameException e) { + assertThat(e.getMessage()).isEqualTo(messages[i]); + } + } + } + + byte b(int b) { + return (byte) b; + } + + byte end() { + return (byte) AMQP.FRAME_END; + } +} diff --git a/src/test/java/com/rabbitmq/client/test/FrameTest.java b/src/test/java/com/rabbitmq/client/test/FrameTest.java new file mode 100644 index 0000000000..fae132263b --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/FrameTest.java @@ -0,0 +1,111 @@ +package com.rabbitmq.client.test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.impl.Frame; +import com.rabbitmq.client.impl.nio.ByteBufferOutputStream; +import org.junit.jupiter.api.Test; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + */ +public class FrameTest { + + @Test + public void writeFrames() throws IOException { + List frames = new ArrayList(); + Random random = new Random(); + int totalFrameSize = 0; + for(int i = 0; i < 100; i++) { + byte[] payload = new byte[random.nextInt(2000) + 1]; + Frame frame = new Frame(AMQP.FRAME_METHOD, 1, payload); + frames.add(frame); + totalFrameSize += frame.size(); + } + + AccumulatorWritableByteChannel channel = new AccumulatorWritableByteChannel(); + ByteBuffer buffer = ByteBuffer.allocate(8192); + + for (Frame frame : frames) { + frame.writeTo(new DataOutputStream(new ByteBufferOutputStream(channel, buffer))); + } + drain(channel, buffer); + checkWrittenChunks(totalFrameSize, channel); + } + + @Test public void writeLargeFrame() throws IOException { + List frames = new ArrayList(); + int totalFrameSize = 0; + int [] framesSize = new int [] {100, 75, 20000, 150}; + for (int frameSize : framesSize) { + Frame frame = new Frame(AMQP.FRAME_METHOD, 1, new byte[frameSize]); + frames.add(frame); + totalFrameSize += frame.size(); + } + + AccumulatorWritableByteChannel channel = new AccumulatorWritableByteChannel(); + ByteBuffer buffer = ByteBuffer.allocate(8192); + + for (Frame frame : frames) { + frame.writeTo(new DataOutputStream(new ByteBufferOutputStream(channel, buffer))); + } + drain(channel, buffer); + checkWrittenChunks(totalFrameSize, channel); + } + + private void checkWrittenChunks(int totalFrameSize, AccumulatorWritableByteChannel channel) { + int totalWritten = 0; + for (byte[] chunk : channel.chunks) { + totalWritten += chunk.length; + } + assertThat(totalWritten).isEqualTo(totalFrameSize); + } + + private static class AccumulatorWritableByteChannel implements WritableByteChannel { + + List chunks = new ArrayList(); + + Random random = new Random(); + + @Override + public int write(ByteBuffer src) throws IOException { + int remaining = src.remaining(); + if(remaining > 0) { + int toRead = random.nextInt(remaining) + 1; + byte [] chunk = new byte[toRead]; + src.get(chunk); + chunks.add(chunk); + return toRead; + } else { + return remaining; + } + + } + + @Override + public boolean isOpen() { + return false; + } + + @Override + public void close() throws IOException { + + } + } + + public static void drain(WritableByteChannel channel, ByteBuffer buffer) throws IOException { + buffer.flip(); + while(buffer.hasRemaining() && channel.write(buffer) != -1); + buffer.clear(); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/GeneratedClassesTest.java b/src/test/java/com/rabbitmq/client/test/GeneratedClassesTest.java new file mode 100644 index 0000000000..c67ad3abae --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/GeneratedClassesTest.java @@ -0,0 +1,135 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.impl.AMQImpl; +import org.junit.jupiter.api.Test; + +import java.util.Calendar; +import java.util.Date; + +import static java.util.Collections.singletonMap; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +/** + * + */ +public class GeneratedClassesTest { + + @Test + public void amqpPropertiesEqualsHashCode() { + checkEquals( + new AMQP.BasicProperties.Builder().correlationId("one").build(), + new AMQP.BasicProperties.Builder().correlationId("one").build() + ); + checkNotEquals( + new AMQP.BasicProperties.Builder().correlationId("one").build(), + new AMQP.BasicProperties.Builder().correlationId("two").build() + ); + Date date = Calendar.getInstance().getTime(); + checkEquals( + new AMQP.BasicProperties.Builder() + .deliveryMode(1) + .headers(singletonMap("one", "two")) + .correlationId("123") + .expiration("later") + .priority(10) + .replyTo("me") + .contentType("text/plain") + .contentEncoding("UTF-8") + .userId("jdoe") + .appId("app1") + .clusterId("cluster1") + .messageId("message123") + .timestamp(date) + .type("type") + .build(), + new AMQP.BasicProperties.Builder() + .deliveryMode(1) + .headers(singletonMap("one", "two")) + .correlationId("123") + .expiration("later") + .priority(10) + .replyTo("me") + .contentType("text/plain") + .contentEncoding("UTF-8") + .userId("jdoe") + .appId("app1") + .clusterId("cluster1") + .messageId("message123") + .timestamp(date) + .type("type") + .build() + ); + checkNotEquals( + new AMQP.BasicProperties.Builder() + .deliveryMode(1) + .headers(singletonMap("one", "two")) + .correlationId("123") + .expiration("later") + .priority(10) + .replyTo("me") + .contentType("text/plain") + .contentEncoding("UTF-8") + .userId("jdoe") + .appId("app1") + .clusterId("cluster1") + .messageId("message123") + .timestamp(date) + .type("type") + .build(), + new AMQP.BasicProperties.Builder() + .deliveryMode(2) + .headers(singletonMap("one", "two")) + .correlationId("123") + .expiration("later") + .priority(10) + .replyTo("me") + .contentType("text/plain") + .contentEncoding("UTF-8") + .userId("jdoe") + .appId("app1") + .clusterId("cluster1") + .messageId("message123") + .timestamp(date) + .type("type") + .build() + ); + + } + + @Test public void amqImplEqualsHashCode() { + checkEquals( + new AMQImpl.Basic.Deliver("tag", 1L, false, "amq.direct","rk"), + new AMQImpl.Basic.Deliver("tag", 1L, false, "amq.direct","rk") + ); + checkNotEquals( + new AMQImpl.Basic.Deliver("tag", 1L, false, "amq.direct","rk"), + new AMQImpl.Basic.Deliver("tag", 2L, false, "amq.direct","rk") + ); + } + + private void checkEquals(Object o1, Object o2) { + assertEquals(o1, o2); + assertEquals(o1.hashCode(), o2.hashCode()); + } + + private void checkNotEquals(Object o1, Object o2) { + assertNotEquals(o1, o2); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/JavaNioTest.java b/src/test/java/com/rabbitmq/client/test/JavaNioTest.java new file mode 100644 index 0000000000..cdc095e40c --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/JavaNioTest.java @@ -0,0 +1,251 @@ +package com.rabbitmq.client.test; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.nio.BlockingQueueNioQueue; +import com.rabbitmq.client.impl.nio.DefaultByteBufferFactory; +import com.rabbitmq.client.impl.nio.NioParams; +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +public class JavaNioTest { + + public static final String QUEUE = "nio.queue"; + + private Connection testConnection; + + @BeforeEach + public void init() throws Exception { + ConnectionFactory connectionFactory = new ConnectionFactory() + .useNio(); + testConnection = connectionFactory.newConnection(); + } + + @AfterEach + public void tearDown() throws Exception { + if (testConnection != null) { + testConnection.createChannel().queueDelete(QUEUE); + testConnection.close(); + } + } + + @Test + public void connection() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + ConnectionFactory connectionFactory = new ConnectionFactory() + .useNio(); + Connection connection = null; + try { + connection = basicGetBasicConsume(connectionFactory, "nio.queue", latch); + boolean messagesReceived = latch.await(5, TimeUnit.SECONDS); + assertTrue(messagesReceived, "Message has not been received"); + } finally { + safeClose(connection); + } + } + + @Test + public void twoConnections() throws IOException, TimeoutException, InterruptedException { + CountDownLatch latch = new CountDownLatch(2); + ConnectionFactory connectionFactory = new ConnectionFactory() + .useNio() + .setNioParams(new NioParams().setNbIoThreads(4)); + Connection connection1 = null; + Connection connection2 = null; + try { + connection1 = basicGetBasicConsume(connectionFactory, "nio.queue.1", latch); + connection2 = basicGetBasicConsume(connectionFactory, "nio.queue.2", latch); + + boolean messagesReceived = latch.await(5, TimeUnit.SECONDS); + assertTrue(messagesReceived, "Messages have not been received"); + } finally { + safeClose(connection1); + safeClose(connection2); + } + } + + @Test + public void twoConnectionsWithNioExecutor() throws IOException, TimeoutException, InterruptedException { + CountDownLatch latch = new CountDownLatch(2); + ExecutorService nioExecutor = Executors.newFixedThreadPool(5); + ConnectionFactory connectionFactory = new ConnectionFactory() + .useNio(); + Connection connection1 = null; + Connection connection2 = null; + try { + connection1 = basicGetBasicConsume(connectionFactory, "nio.queue.1", latch); + connection2 = basicGetBasicConsume(connectionFactory, "nio.queue.2", latch); + + boolean messagesReceived = latch.await(5, TimeUnit.SECONDS); + assertTrue(messagesReceived, "Messages have not been received"); + } finally { + safeClose(connection1); + safeClose(connection2); + nioExecutor.shutdownNow(); + } + } + + @Test + public void shutdownListenerCalled() throws IOException, TimeoutException, InterruptedException { + ConnectionFactory connectionFactory = new ConnectionFactory() + .useNio(); + Connection connection = connectionFactory.newConnection(); + try { + final CountDownLatch latch = new CountDownLatch(1); + connection.addShutdownListener(new ShutdownListener() { + + @Override + public void shutdownCompleted(ShutdownSignalException cause) { + latch.countDown(); + } + }); + safeClose(connection); + assertTrue(latch.await(5, TimeUnit.SECONDS), "Shutdown listener should have been called"); + } finally { + safeClose(connection); + } + } + + @Test + public void nioLoopCleaning() throws Exception { + ConnectionFactory connectionFactory = new ConnectionFactory() + .useNio(); + for (int i = 0; i < 10; i++) { + Connection connection = connectionFactory.newConnection(); + connection.abort(); + } + } + + @Test + public void messageSize() throws Exception { + for (int i = 0; i < 50; i++) { + sendAndVerifyMessage(testConnection, 76390); + } + } + + @Test + public void byteBufferFactory() throws Exception { + ConnectionFactory connectionFactory = new ConnectionFactory() + .useNio(); + int baseCapacity = 32768; + NioParams nioParams = new NioParams(); + nioParams.setReadByteBufferSize(baseCapacity / 2); + nioParams.setWriteByteBufferSize(baseCapacity / 4); + List byteBuffers = new CopyOnWriteArrayList<>(); + connectionFactory.setNioParams(nioParams.setByteBufferFactory(new DefaultByteBufferFactory(capacity -> { + ByteBuffer bb = ByteBuffer.allocate(capacity); + byteBuffers.add(bb); + return bb; + }))); + + try (Connection c = connectionFactory.newConnection()) { + sendAndVerifyMessage(c, 100); + } + + assertThat(byteBuffers).hasSize(2); + Condition condition = new Condition<>(c -> c == nioParams.getReadByteBufferSize() || + c == nioParams.getWriteByteBufferSize(), "capacity set by factory"); + assertThat(byteBuffers.get(0).capacity()).is(condition); + assertThat(byteBuffers.get(1).capacity()).is(condition); + } + + @Test + public void directByteBuffers() throws Exception { + ConnectionFactory connectionFactory = new ConnectionFactory() + .useNio() + .setNioParams(new NioParams().setByteBufferFactory(new DefaultByteBufferFactory(capacity -> ByteBuffer.allocateDirect(capacity)))); + try (Connection c = connectionFactory.newConnection()) { + sendAndVerifyMessage(c, 100); + } + } + + @Test + public void customWriteQueue() throws Exception { + AtomicInteger count = new AtomicInteger(0); + ConnectionFactory connectionFactory = new ConnectionFactory() + .useNio() + .setNioParams(new NioParams().setWriteQueueFactory(ctx -> { + count.incrementAndGet(); + return new BlockingQueueNioQueue( + new LinkedBlockingQueue<>(ctx.getNioParams().getWriteQueueCapacity()), + ctx.getNioParams().getWriteEnqueuingTimeoutInMs() + ); + })); + try (Connection c = connectionFactory.newConnection()) { + sendAndVerifyMessage(c, 100); + } + assertEquals(1, count.get()); + } + + private void sendAndVerifyMessage(Connection connection, int size) throws Exception { + CountDownLatch latch = new CountDownLatch(1); + boolean messageReceived = basicGetBasicConsume(connection, QUEUE, latch, size); + assertTrue(messageReceived, "Message has not been received"); + } + + private Connection basicGetBasicConsume(ConnectionFactory connectionFactory, String queue, final CountDownLatch latch) + throws IOException, TimeoutException { + Connection connection = connectionFactory.newConnection(); + Channel channel = connection.createChannel(); + channel.queueDeclare(queue, false, false, false, null); + channel.queuePurge(queue); + + channel.basicPublish("", queue, null, new byte[20000]); + + channel.basicConsume(queue, false, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + getChannel().basicAck(envelope.getDeliveryTag(), false); + latch.countDown(); + } + }); + + return connection; + } + + private boolean basicGetBasicConsume(Connection connection, String queue, final CountDownLatch latch, int msgSize) + throws Exception { + Channel channel = connection.createChannel(); + channel.queueDeclare(queue, false, false, false, null); + channel.queuePurge(queue); + + channel.basicPublish("", queue, null, new byte[msgSize]); + + final String tag = channel.basicConsume(queue, false, new DefaultConsumer(channel) { + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + getChannel().basicAck(envelope.getDeliveryTag(), false); + latch.countDown(); + } + }); + + boolean done = latch.await(20, TimeUnit.SECONDS); + channel.basicCancel(tag); + return done; + } + + private void safeClose(Connection connection) { + if (connection != null) { + try { + connection.abort(); + } catch (Exception e) { + // OK + } + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/LambdaCallbackTest.java b/src/test/java/com/rabbitmq/client/test/LambdaCallbackTest.java new file mode 100644 index 0000000000..1fb6c2e826 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/LambdaCallbackTest.java @@ -0,0 +1,146 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LambdaCallbackTest extends BrokerTestCase { + + String queue; + + @Override + protected void createResources() throws IOException, TimeoutException { + queue = channel.queueDeclare(UUID.randomUUID().toString(), true, false, false, null).getQueue(); + } + + @Override + protected void releaseResources() throws IOException { + channel.queueDelete(queue); + try { + unblock(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Test public void shutdownListener() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + try(Connection connection = TestUtils.connectionFactory().newConnection()) { + connection.addShutdownListener(cause -> latch.countDown()); + Channel channel = connection.createChannel(); + channel.addShutdownListener(cause -> latch.countDown()); + } + assertTrue(latch.await(1, TimeUnit.SECONDS), "Connection closed, shutdown listeners should have been called"); + } + + @Test public void confirmListener() throws Exception { + channel.confirmSelect(); + CountDownLatch latch = new CountDownLatch(1); + channel.addConfirmListener( + (deliveryTag, multiple) -> latch.countDown(), + (deliveryTag, multiple) -> {} + ); + channel.basicPublish("", "whatever", null, "dummy".getBytes()); + assertTrue(latch.await(1, TimeUnit.SECONDS), "Should have received publisher confirm"); + } + + @Test public void returnListener() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + channel.addReturnListener(returnMessage -> latch.countDown()); + channel.basicPublish("", "notlikelytoexist", true, null, "dummy".getBytes()); + assertTrue(latch.await(1, TimeUnit.SECONDS), "Should have received returned message"); + } + + @Test public void blockedListener() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + try(Connection connection = TestUtils.connectionFactory().newConnection()) { + connection.addBlockedListener( + reason -> { + try { + unblock(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }, + () -> latch.countDown() + ); + block(); + Channel ch = connection.createChannel(); + ch.basicPublish("", "", null, "dummy".getBytes()); + assertTrue(latch.await(10, TimeUnit.SECONDS), "Should have been blocked and unblocked"); + } + } + + @Test public void basicConsumeDeliverCancel() throws Exception { + try(Connection connection = TestUtils.connectionFactory().newConnection()) { + final CountDownLatch consumingLatch = new CountDownLatch(1); + final CountDownLatch cancelLatch = new CountDownLatch(1); + Channel consumingChannel = connection.createChannel(); + consumingChannel.basicConsume(queue, true, + (consumerTag, delivery) -> consumingLatch.countDown(), + consumerTag -> cancelLatch.countDown() + ); + this.channel.basicPublish("", queue, null, "dummy".getBytes()); + assertTrue(consumingLatch.await(1, TimeUnit.SECONDS), "deliver callback should have been called"); + this.channel.queueDelete(queue); + assertTrue(cancelLatch.await(1, TimeUnit.SECONDS), "cancel callback should have been called"); + } + } + + @Test public void basicConsumeDeliverShutdown() throws Exception { + final CountDownLatch shutdownLatch = new CountDownLatch(1); + try(Connection connection = TestUtils.connectionFactory().newConnection()) { + final CountDownLatch consumingLatch = new CountDownLatch(1); + Channel consumingChannel = connection.createChannel(); + consumingChannel.basicConsume(queue, true, + (consumerTag, delivery) -> consumingLatch.countDown(), + (consumerTag, sig) -> shutdownLatch.countDown() + ); + this.channel.basicPublish("", queue, null, "dummy".getBytes()); + assertTrue(consumingLatch.await(1, TimeUnit.SECONDS), "deliver callback should have been called"); + } + assertTrue(shutdownLatch.await(1, TimeUnit.SECONDS), "shutdown callback should have been called"); + } + + @Test public void basicConsumeCancelDeliverShutdown() throws Exception { + final CountDownLatch shutdownLatch = new CountDownLatch(1); + try(Connection connection = TestUtils.connectionFactory().newConnection()) { + final CountDownLatch consumingLatch = new CountDownLatch(1); + Channel consumingChannel = connection.createChannel(); + // not both cancel and shutdown callback can be called on the same consumer + // testing just shutdown + consumingChannel.basicConsume(queue, true, + (consumerTag, delivery) -> consumingLatch.countDown(), + (consumerTag) -> { }, + (consumerTag, sig) -> shutdownLatch.countDown() + ); + this.channel.basicPublish("", queue, null, "dummy".getBytes()); + assertTrue(consumingLatch.await(1, TimeUnit.SECONDS), "deliver callback should have been called"); + } + assertTrue(shutdownLatch.await(1, TimeUnit.SECONDS), "shutdown callback should have been called"); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/LongStringTest.java b/src/test/java/com/rabbitmq/client/test/LongStringTest.java new file mode 100644 index 0000000000..34b129ba93 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/LongStringTest.java @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.LongString; +import com.rabbitmq.client.impl.LongStringHelper; +import org.junit.jupiter.api.Test; + +import java.io.UnsupportedEncodingException; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LongStringTest { + + @Test public void testToString() throws UnsupportedEncodingException { + String s = "abcdef"; + LongString ls = LongStringHelper.asLongString(s); + + assertTrue(ls.toString().equals(s)); + assertTrue(ls.toString().equals(new String(ls.getBytes(), "UTF-8"))); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/MaxInboundMessageSizeTest.java b/src/test/java/com/rabbitmq/client/test/MaxInboundMessageSizeTest.java new file mode 100644 index 0000000000..b1ee0b86f2 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/MaxInboundMessageSizeTest.java @@ -0,0 +1,175 @@ +// Copyright (c) 2023-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom +// Inc. +// and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import static com.rabbitmq.client.test.TestUtils.LatchConditions.completed; +import static org.assertj.core.api.Assertions.assertThat; + +import com.rabbitmq.client.*; +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class MaxInboundMessageSizeTest extends BrokerTestCase { + + String q; + + private static void safeClose(Connection c) { + try { + c.close(); + } catch (Exception e) { + // OK + } + } + + @Override + protected void createResources() throws IOException, TimeoutException { + q = generateQueueName(); + declareTransientQueue(q); + super.createResources(); + } + + @CsvSource({ + "20000,5000,true", + "20000,100000,true", + "20000,5000,false", + "20000,100000,false", + }) + @ParameterizedTest + void maxInboundMessageSizeMustBeEnforced(int maxMessageSize, int frameMax, boolean basicGet) + throws Exception { + ConnectionFactory cf = newConnectionFactory(); + cf.setMaxInboundMessageBodySize(maxMessageSize); + cf.setRequestedFrameMax(frameMax); + Connection c = cf.newConnection(); + try { + Channel ch = c.createChannel(); + ch.confirmSelect(); + byte[] body = new byte[maxMessageSize * 2]; + ch.basicPublish("", q, null, body); + ch.waitForConfirmsOrDie(); + AtomicReference channelException = new AtomicReference<>(); + CountDownLatch channelErrorLatch = new CountDownLatch(1); + ch.addShutdownListener( + cause -> { + channelException.set(cause.getCause()); + channelErrorLatch.countDown(); + }); + AtomicReference connectionException = new AtomicReference<>(); + CountDownLatch connectionErrorLatch = new CountDownLatch(1); + c.addShutdownListener( + cause -> { + connectionException.set(cause.getCause()); + connectionErrorLatch.countDown(); + }); + if (basicGet) { + try { + ch.basicGet(q, true); + } catch (Exception e) { + // OK for basicGet + } + } else { + ch.basicConsume(q, new DefaultConsumer(ch)); + } + assertThat(channelErrorLatch).is(completed()); + assertThat(channelException.get()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Message body is too large"); + assertThat(connectionErrorLatch).is(completed()); + assertThat(connectionException.get()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Message body is too large"); + } finally { + safeClose(c); + } + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void largeMessageShouldGoBackToQueue(boolean basicGet) throws Exception { + int maxMessageSize = 5_000; + int maxFrameSize = maxMessageSize * 4; + ConnectionFactory cf = newConnectionFactory(); + cf.setMaxInboundMessageBodySize(maxMessageSize); + cf.setRequestedFrameMax(maxFrameSize); + String messageId = UUID.randomUUID().toString(); + Connection c = cf.newConnection(); + try { + Channel ch = c.createChannel(); + ch.confirmSelect(); + AMQP.BasicProperties.Builder propsBuilder = new AMQP.BasicProperties.Builder(); + propsBuilder.messageId(messageId); + byte[] body = new byte[maxMessageSize * 2]; + ch.basicPublish("", q, propsBuilder.build(), body); + ch.waitForConfirmsOrDie(); + AtomicReference exception = new AtomicReference<>(); + CountDownLatch errorLatch = new CountDownLatch(1); + ch.addShutdownListener( + cause -> { + exception.set(cause.getCause()); + errorLatch.countDown(); + }); + if (basicGet) { + try { + ch.basicGet(q, false); + } catch (Exception e) { + // OK for basicGet + } + } else { + ch.basicConsume(q, false, new DefaultConsumer(ch)); + } + assertThat(errorLatch).is(completed()); + assertThat(exception.get()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Message body is too large"); + } finally { + safeClose(c); + } + + cf = newConnectionFactory(); + cf.setMaxInboundMessageBodySize(maxMessageSize * 3); + cf.setRequestedFrameMax(maxFrameSize * 3); + try (Connection conn = cf.newConnection()) { + AtomicReference receivedMessageId = new AtomicReference<>(); + Channel ch = conn.createChannel(); + CountDownLatch consumeLatch = new CountDownLatch(1); + ch.basicConsume( + q, + true, + (consumerTag, message) -> { + receivedMessageId.set(message.getProperties().getMessageId()); + consumeLatch.countDown(); + }, + consumerTag -> {}); + + assertThat(consumeLatch).is(completed()); + assertThat(receivedMessageId).hasValue(messageId); + } + } + + @Override + protected void releaseResources() throws IOException { + deleteQueue(q); + super.releaseResources(); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/MetricsCollectorTest.java b/src/test/java/com/rabbitmq/client/test/MetricsCollectorTest.java new file mode 100644 index 0000000000..8a59732787 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/MetricsCollectorTest.java @@ -0,0 +1,537 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.MetricsCollector; +import com.rabbitmq.client.impl.AbstractMetricsCollector; +import com.rabbitmq.client.impl.MicrometerMetricsCollector; +import com.rabbitmq.client.impl.OpenTelemetryMetricsCollector; +import com.rabbitmq.client.impl.StandardMetricsCollector; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.util.List; +import java.util.function.LongConsumer; +import java.util.stream.LongStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * + */ +public class MetricsCollectorTest { + + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + public static Object[] data() { + // need to resort to a factory, as this method is called only once + // if creating the collector instance, it's reused across the test methods + // and this doesn't work (it cannot be reset) + return new Object[]{new StandardMetricsCollectorFactory(), new MicrometerMetricsCollectorFactory(), new OpenTelemetryMetricsCollectorFactory()}; + } + + @BeforeEach + public void reset() { + // reset metrics + otelTesting.clearMetrics(); + } + + @ParameterizedTest + @MethodSource("data") + public void basicGetAndAck(MetricsCollectorFactory factory) { + AbstractMetricsCollector metrics = factory.create(); + Connection connection = mock(Connection.class); + when(connection.getId()).thenReturn("connection-1"); + Channel channel = mock(Channel.class); + when(channel.getConnection()).thenReturn(connection); + when(channel.getChannelNumber()).thenReturn(1); + + metrics.newConnection(connection); + metrics.newChannel(channel); + + metrics.consumedMessage(channel, 1, true); + metrics.consumedMessage(channel, 2, false); + metrics.consumedMessage(channel, 3, false); + metrics.consumedMessage(channel, 4, true); + metrics.consumedMessage(channel, 5, false); + metrics.consumedMessage(channel, 6, false); + + metrics.basicAck(channel, 6, false); + assertThat(acknowledgedMessages(metrics)).isEqualTo(1L); + + metrics.basicAck(channel, 3, true); + assertThat(acknowledgedMessages(metrics)).isEqualTo(1L+2L); + + metrics.basicAck(channel, 6, true); + assertThat(acknowledgedMessages(metrics)).isEqualTo(1L+2L+1L); + + metrics.basicAck(channel, 10, true); + assertThat(acknowledgedMessages(metrics)).isEqualTo(1L+2L+1L); + } + + @ParameterizedTest + @MethodSource("data") + public void basicConsumeAndAck(MetricsCollectorFactory factory) { + AbstractMetricsCollector metrics = factory.create(); + Connection connection = mock(Connection.class); + when(connection.getId()).thenReturn("connection-1"); + Channel channel = mock(Channel.class); + when(channel.getConnection()).thenReturn(connection); + when(channel.getChannelNumber()).thenReturn(1); + + metrics.newConnection(connection); + metrics.newChannel(channel); + + String consumerTagWithAutoAck = "1"; + String consumerTagWithManualAck = "2"; + metrics.basicConsume(channel, consumerTagWithAutoAck, true); + metrics.basicConsume(channel, consumerTagWithManualAck, false); + + metrics.consumedMessage(channel, 1, consumerTagWithAutoAck); + assertThat(consumedMessages(metrics)).isEqualTo(1L); + assertThat(acknowledgedMessages(metrics)).isEqualTo(0L); + + metrics.consumedMessage(channel, 2, consumerTagWithManualAck); + metrics.consumedMessage(channel, 3, consumerTagWithManualAck); + metrics.consumedMessage(channel, 4, consumerTagWithAutoAck); + metrics.consumedMessage(channel, 5, consumerTagWithManualAck); + metrics.consumedMessage(channel, 6, consumerTagWithManualAck); + + metrics.basicAck(channel, 6, false); + assertThat(acknowledgedMessages(metrics)).isEqualTo(1L); + + metrics.basicAck(channel, 3, true); + assertThat(acknowledgedMessages(metrics)).isEqualTo(1L+2L); + + metrics.basicAck(channel, 6, true); + assertThat(acknowledgedMessages(metrics)).isEqualTo(1L+2L+1L); + + metrics.basicAck(channel, 10, true); + assertThat(acknowledgedMessages(metrics)).isEqualTo(1L+2L+1L); + } + + @ParameterizedTest + @MethodSource("data") + public void basicConsumeAndNackReject(MetricsCollectorFactory factory) { + AbstractMetricsCollector metrics = factory.create(); + Connection connection = mock(Connection.class); + when(connection.getId()).thenReturn("connection-1"); + Channel channel = mock(Channel.class); + when(channel.getConnection()).thenReturn(connection); + when(channel.getChannelNumber()).thenReturn(1); + + metrics.newConnection(connection); + metrics.newChannel(channel); + + String ctag = "1"; + metrics.basicConsume(channel, ctag, false); + + LongConsumer consumed = dtag -> metrics.consumedMessage(channel, dtag, ctag); + long count = 10; + LongStream.range(0, count).forEach(consumed::accept) ; + assertThat(consumedMessages(metrics)).isEqualTo(count); + assertThat(acknowledgedMessages(metrics)).isZero(); + + metrics.basicReject(channel, 0, false); + assertThat(acknowledgedMessages(metrics)).isZero(); + assertThat(rejectedMessages(metrics)).isEqualTo(1L); + assertThat(requeuedMessages(metrics)).isZero(); + + metrics.basicReject(channel, 1, true); + assertThat(acknowledgedMessages(metrics)).isZero(); + assertThat(rejectedMessages(metrics)).isEqualTo(2L); + assertThat(requeuedMessages(metrics)).isEqualTo(1L); + + metrics.basicNack(channel, 4, false); + assertThat(acknowledgedMessages(metrics)).isZero(); + assertThat(rejectedMessages(metrics)).isEqualTo(2L + 3L); + assertThat(requeuedMessages(metrics)).isEqualTo(1L); + + metrics.basicNack(channel, 7, true); + assertThat(acknowledgedMessages(metrics)).isZero(); + assertThat(rejectedMessages(metrics)).isEqualTo(2L + 3L + 3L); + assertThat(requeuedMessages(metrics)).isEqualTo(1L + 3L); + + metrics.basicAck(channel, 9, true); + assertThat(acknowledgedMessages(metrics)).isEqualTo(2); + assertThat(rejectedMessages(metrics)).isEqualTo(2L + 3L + 3L); + assertThat(requeuedMessages(metrics)).isEqualTo(1L + 3L); + } + + @ParameterizedTest + @MethodSource("data") + public void publishingAndPublishingFailures(MetricsCollectorFactory factory) { + AbstractMetricsCollector metrics = factory.create(); + Channel channel = mock(Channel.class); + + assertThat(failedToPublishMessages(metrics)).isEqualTo(0L); + assertThat(publishedMessages(metrics)).isEqualTo(0L); + + metrics.basicPublishFailure(channel, new IOException()); + assertThat(failedToPublishMessages(metrics)).isEqualTo(1L); + assertThat(publishedMessages(metrics)).isEqualTo(0L); + + metrics.basicPublish(channel, 0L); + assertThat(failedToPublishMessages(metrics)).isEqualTo(1L); + assertThat(publishedMessages(metrics)).isEqualTo(1L); + + metrics.basicPublishFailure(channel, new IOException()); + assertThat(failedToPublishMessages(metrics)).isEqualTo(2L); + assertThat(publishedMessages(metrics)).isEqualTo(1L); + + metrics.basicPublish(channel, 0L); + assertThat(failedToPublishMessages(metrics)).isEqualTo(2L); + assertThat(publishedMessages(metrics)).isEqualTo(2L); + + metrics.cleanStaleState(); + assertThat(failedToPublishMessages(metrics)).isEqualTo(2L); + assertThat(publishedMessages(metrics)).isEqualTo(2L); + } + + @ParameterizedTest + @MethodSource("data") + public void publishingAcknowledgements(MetricsCollectorFactory factory) { + AbstractMetricsCollector metrics = factory.create(); + Connection connection = mock(Connection.class); + when(connection.getId()).thenReturn("connection-1"); + Channel channel = mock(Channel.class); + when(channel.getConnection()).thenReturn(connection); + when(channel.getChannelNumber()).thenReturn(1); + + metrics.newConnection(connection); + metrics.newChannel(channel); + + // begins with no messages acknowledged + assertThat(publishAck(metrics)).isEqualTo(0L); + // first acknowledgement gets tracked + metrics.basicPublish(channel, 1); + metrics.basicPublishAck(channel, 1, false); + assertThat(publishAck(metrics)).isEqualTo(1L); + // second acknowledgement gets tracked + metrics.basicPublish(channel, 2); + metrics.basicPublishAck(channel, 2, false); + assertThat(publishAck(metrics)).isEqualTo(2L); + + // it's not idempotent + metrics.basicPublishAck(channel, 2, false); + assertThat(publishAck(metrics)).isEqualTo(3L); + + // multi-ack + metrics.basicPublish(channel, 3); + metrics.basicPublish(channel, 4); + metrics.basicPublish(channel, 5); + // ack-ing in the middle + metrics.basicPublishAck(channel, 4, false); + assertThat(publishAck(metrics)).isEqualTo(4L); + // ack-ing several at once + metrics.basicPublishAck(channel, 5, true); + assertThat(publishAck(metrics)).isEqualTo(6L); + + // ack-ing non existent doesn't affect metrics + metrics.basicPublishAck(channel, 123, true); + assertThat(publishAck(metrics)).isEqualTo(6L); + + // cleaning stale state doesn't affect the metric + metrics.cleanStaleState(); + assertThat(publishAck(metrics)).isEqualTo(6L); + } + + @ParameterizedTest + @MethodSource("data") + public void publishingNotAcknowledgements(MetricsCollectorFactory factory) { + AbstractMetricsCollector metrics = factory.create(); + Connection connection = mock(Connection.class); + when(connection.getId()).thenReturn("connection-1"); + Channel channel = mock(Channel.class); + when(channel.getConnection()).thenReturn(connection); + when(channel.getChannelNumber()).thenReturn(1); + + metrics.newConnection(connection); + metrics.newChannel(channel); + // begins with no messages not-acknowledged + assertThat(publishNack(metrics)).isEqualTo(0L); + // first not-acknowledgement gets tracked + metrics.basicPublish(channel, 1); + metrics.basicPublishNack(channel, 1, false); + assertThat(publishNack(metrics)).isEqualTo(1L); + // second not-acknowledgement gets tracked + metrics.basicPublish(channel, 2); + metrics.basicPublishNack(channel, 2, false); + assertThat(publishNack(metrics)).isEqualTo(2L); + + // multi-nack + metrics.basicPublish(channel, 3); + metrics.basicPublish(channel, 4); + metrics.basicPublish(channel, 5); + // ack-ing in the middle + metrics.basicPublishNack(channel, 4, false); + assertThat(publishNack(metrics)).isEqualTo(3L); + // ack-ing several at once + metrics.basicPublishNack(channel, 5, true); + assertThat(publishNack(metrics)).isEqualTo(5L); + + // ack-ing non existent doesn't affect metrics + metrics.basicPublishNack(channel, 123, true); + assertThat(publishNack(metrics)).isEqualTo(5L); + + // cleaning stale state doesn't affect the metric + metrics.cleanStaleState(); + assertThat(publishNack(metrics)).isEqualTo(5L); + } + + @ParameterizedTest + @MethodSource("data") + public void publishingUnrouted(MetricsCollectorFactory factory) { + AbstractMetricsCollector metrics = factory.create(); + Channel channel = mock(Channel.class); + // begins with no messages not-acknowledged + assertThat(publishUnrouted(metrics)).isEqualTo(0L); + // first unrouted gets tracked + metrics.basicPublishUnrouted(channel); + assertThat(publishUnrouted(metrics)).isEqualTo(1L); + // second unrouted gets tracked + metrics.basicPublishUnrouted(channel); + assertThat(publishUnrouted(metrics)).isEqualTo(2L); + // cleaning stale state doesn't affect the metric + metrics.cleanStaleState(); + assertThat(publishUnrouted(metrics)).isEqualTo(2L); + } + + @ParameterizedTest + @MethodSource("data") + public void cleanStaleState(MetricsCollectorFactory factory) { + AbstractMetricsCollector metrics = factory.create(); + Connection openConnection = mock(Connection.class); + when(openConnection.getId()).thenReturn("connection-1"); + when(openConnection.isOpen()).thenReturn(true); + + Channel openChannel = mock(Channel.class); + when(openChannel.getConnection()).thenReturn(openConnection); + when(openChannel.getChannelNumber()).thenReturn(1); + when(openChannel.isOpen()).thenReturn(true); + + Channel closedChannel = mock(Channel.class); + when(closedChannel.getConnection()).thenReturn(openConnection); + when(closedChannel.getChannelNumber()).thenReturn(2); + when(closedChannel.isOpen()).thenReturn(false); + + Connection closedConnection = mock(Connection.class); + when(closedConnection.getId()).thenReturn("connection-2"); + when(closedConnection.isOpen()).thenReturn(false); + + Channel openChannelInClosedConnection = mock(Channel.class); + when(openChannelInClosedConnection.getConnection()).thenReturn(closedConnection); + when(openChannelInClosedConnection.getChannelNumber()).thenReturn(1); + when(openChannelInClosedConnection.isOpen()).thenReturn(true); + + metrics.newConnection(openConnection); + metrics.newConnection(closedConnection); + metrics.newChannel(openChannel); + metrics.newChannel(closedChannel); + metrics.newChannel(openChannelInClosedConnection); + + assertThat(connections(metrics)).isEqualTo(2L); + assertThat(channels(metrics)).isEqualTo(2L+1L); + + metrics.cleanStaleState(); + + assertThat(connections(metrics)).isEqualTo(1L); + assertThat(channels(metrics)).isEqualTo(1L); + } + + + long publishAck(MetricsCollector metrics) { + if (metrics instanceof StandardMetricsCollector) { + return ((StandardMetricsCollector) metrics).getPublishAcknowledgedMessages().getCount(); + } + else if (metrics instanceof MicrometerMetricsCollector) { + return (long)((MicrometerMetricsCollector) metrics).getAckedPublishedMessages().count(); + } + else { + return getOpenTelemetryCounterMeterValue("rabbitmq.acknowledged_published"); + } + } + + long publishNack(MetricsCollector metrics) { + if (metrics instanceof StandardMetricsCollector) { + return ((StandardMetricsCollector) metrics).getPublishNotAcknowledgedMessages().getCount(); + } + else if (metrics instanceof MicrometerMetricsCollector) { + return (long)((MicrometerMetricsCollector) metrics).getNackedPublishedMessages().count(); + } + else { + return getOpenTelemetryCounterMeterValue("rabbitmq.not_acknowledged_published"); + } + } + + long publishUnrouted(MetricsCollector metrics) { + if (metrics instanceof StandardMetricsCollector) { + return ((StandardMetricsCollector) metrics).getPublishUnroutedMessages().getCount(); + } + else if (metrics instanceof MicrometerMetricsCollector) { + return (long)((MicrometerMetricsCollector) metrics).getUnroutedPublishedMessages().count(); + } + else { + return getOpenTelemetryCounterMeterValue("rabbitmq.unrouted_published"); + } + } + + long publishedMessages(MetricsCollector metrics) { + if (metrics instanceof StandardMetricsCollector) { + return ((StandardMetricsCollector) metrics).getPublishedMessages().getCount(); + } + else if (metrics instanceof MicrometerMetricsCollector) { + return (long)((MicrometerMetricsCollector) metrics).getPublishedMessages().count(); + } + else { + return getOpenTelemetryCounterMeterValue("rabbitmq.published"); + } + } + + long failedToPublishMessages(MetricsCollector metrics) { + if (metrics instanceof StandardMetricsCollector) { + return ((StandardMetricsCollector) metrics).getFailedToPublishMessages().getCount(); + } + else if (metrics instanceof MicrometerMetricsCollector) { + return (long)((MicrometerMetricsCollector) metrics).getFailedToPublishMessages().count(); + } + else { + return getOpenTelemetryCounterMeterValue("rabbitmq.failed_to_publish"); + } + } + + long consumedMessages(MetricsCollector metrics) { + if (metrics instanceof StandardMetricsCollector) { + return ((StandardMetricsCollector) metrics).getConsumedMessages().getCount(); + } + else if (metrics instanceof MicrometerMetricsCollector) { + return (long)((MicrometerMetricsCollector) metrics).getConsumedMessages().count(); + } + else { + return getOpenTelemetryCounterMeterValue("rabbitmq.consumed"); + } + } + + long acknowledgedMessages(MetricsCollector metrics) { + if (metrics instanceof StandardMetricsCollector) { + return ((StandardMetricsCollector) metrics).getAcknowledgedMessages().getCount(); + } + else if (metrics instanceof MicrometerMetricsCollector) { + return (long)((MicrometerMetricsCollector) metrics).getAcknowledgedMessages().count(); + } + else { + return getOpenTelemetryCounterMeterValue("rabbitmq.acknowledged"); + } + } + + long rejectedMessages(MetricsCollector metrics) { + if (metrics instanceof StandardMetricsCollector) { + return ((StandardMetricsCollector) metrics).getRejectedMessages().getCount(); + } + else if (metrics instanceof MicrometerMetricsCollector) { + return (long)((MicrometerMetricsCollector) metrics).getRejectedMessages().count(); + } + else { + return getOpenTelemetryCounterMeterValue("rabbitmq.rejected"); + } + } + + long requeuedMessages(MetricsCollector metrics) { + if (metrics instanceof StandardMetricsCollector) { + return ((StandardMetricsCollector) metrics).getRequeuedMessages().getCount(); + } + else if (metrics instanceof MicrometerMetricsCollector) { + return (long)((MicrometerMetricsCollector) metrics).getRequeuedMessages().count(); + } + else { + return getOpenTelemetryCounterMeterValue("rabbitmq.requeued"); + } + } + + long connections(MetricsCollector metrics) { + if (metrics instanceof StandardMetricsCollector) { + return ((StandardMetricsCollector) metrics).getConnections().getCount(); + } + else if (metrics instanceof MicrometerMetricsCollector) { + return ((MicrometerMetricsCollector) metrics).getConnections().get(); + } + else { + return ((OpenTelemetryMetricsCollector)metrics).getConnections().get(); + } + } + + long channels(MetricsCollector metrics) { + if (metrics instanceof StandardMetricsCollector) { + return ((StandardMetricsCollector) metrics).getChannels().getCount(); + } + else if (metrics instanceof MicrometerMetricsCollector) { + return ((MicrometerMetricsCollector) metrics).getChannels().get(); + } + else { + return ((OpenTelemetryMetricsCollector)metrics).getChannels().get(); + } + } + + interface MetricsCollectorFactory { + AbstractMetricsCollector create(); + } + + static class StandardMetricsCollectorFactory implements MetricsCollectorFactory { + @Override + public AbstractMetricsCollector create() { + return new StandardMetricsCollector(); + } + } + + static class MicrometerMetricsCollectorFactory implements MetricsCollectorFactory { + @Override + public AbstractMetricsCollector create() { + return new MicrometerMetricsCollector(new SimpleMeterRegistry()); + } + } + + static class OpenTelemetryMetricsCollectorFactory implements MetricsCollectorFactory { + @Override + public AbstractMetricsCollector create() { + return new OpenTelemetryMetricsCollector(otelTesting.getOpenTelemetry()); + } + } + + static long getOpenTelemetryCounterMeterValue(String name) { + // open telemetry metrics + List metrics = otelTesting.getMetrics(); + // metric value + return metrics.stream() + .filter(metric -> metric.getName().equals(name)) + .flatMap(metric -> metric.getData().getPoints().stream()) + .map(point -> (LongPointData)point) + .map(LongPointData::getValue) + .mapToLong(value -> value) + .sum(); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/MicrometerMetricsCollectorTest.java b/src/test/java/com/rabbitmq/client/test/MicrometerMetricsCollectorTest.java new file mode 100644 index 0000000000..b55aff2486 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/MicrometerMetricsCollectorTest.java @@ -0,0 +1,63 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.rabbitmq.client.impl.MicrometerMetricsCollector; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * + */ +public class MicrometerMetricsCollectorTest { + + SimpleMeterRegistry registry; + + MicrometerMetricsCollector collector; + + @BeforeEach + public void init() { + registry = new SimpleMeterRegistry(); + } + + @Test + public void noTag() { + collector = new MicrometerMetricsCollector(registry, "rabbitmq"); + for (Meter meter : registry.getMeters()) { + Assertions.assertThat(meter.getId().getTags()).isEmpty(); + } + } + + @Test + public void tags() { + collector = new MicrometerMetricsCollector(registry, "rabbitmq", "uri", "/api/users"); + for (Meter meter : registry.getMeters()) { + Assertions.assertThat(meter.getId().getTags()).hasSize(1); + } + } + + @Test + public void tagsMustBeKeyValuePairs() { + assertThatThrownBy(() -> new MicrometerMetricsCollector(registry, "rabbitmq", "uri")) + .isInstanceOf(IllegalArgumentException.class); + } + +} diff --git a/test/src/com/rabbitmq/client/test/MultiThreadedChannel.java b/src/test/java/com/rabbitmq/client/test/MultiThreadedChannel.java similarity index 63% rename from test/src/com/rabbitmq/client/test/MultiThreadedChannel.java rename to src/test/java/com/rabbitmq/client/test/MultiThreadedChannel.java index b1a08c4e71..6deea6621e 100644 --- a/test/src/com/rabbitmq/client/test/MultiThreadedChannel.java +++ b/src/test/java/com/rabbitmq/client/test/MultiThreadedChannel.java @@ -1,25 +1,24 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test; -import com.rabbitmq.client.test.BrokerTestCase; - import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.Test; + /** * Tests whether a Channel is safe for multi-threaded access */ @@ -29,7 +28,7 @@ public class MultiThreadedChannel extends BrokerTestCase { private static final String DUMMY_EXCHANGE_NAME = "dummy.exchange"; - public void testInterleavedRpcs() throws Throwable { + @Test public void interleavedRpcs() throws Throwable { final AtomicReference throwableRef = new AtomicReference(null); diff --git a/src/test/java/com/rabbitmq/client/test/NioDeadlockOnConnectionClosing.java b/src/test/java/com/rabbitmq/client/test/NioDeadlockOnConnectionClosing.java new file mode 100644 index 0000000000..50efb34bb0 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/NioDeadlockOnConnectionClosing.java @@ -0,0 +1,98 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.impl.nio.NioParams; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static com.rabbitmq.client.test.TestUtils.closeAllConnectionsAndWaitForRecovery; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +public class NioDeadlockOnConnectionClosing { + + static final Logger LOGGER = LoggerFactory.getLogger(NioDeadlockOnConnectionClosing.class); + + ExecutorService nioExecutorService, connectionShutdownExecutorService; + ConnectionFactory cf; + List connections; + + @BeforeEach + public void setUp() { + nioExecutorService = Executors.newFixedThreadPool(2); + connectionShutdownExecutorService = Executors.newFixedThreadPool(2); + cf = TestUtils.connectionFactory(); + cf.setAutomaticRecoveryEnabled(true); + cf.useNio(); + cf.setNetworkRecoveryInterval(1000); + NioParams params = new NioParams() + .setNioExecutor(nioExecutorService) + .setConnectionShutdownExecutor(connectionShutdownExecutorService) + .setNbIoThreads(2); + cf.setNioParams(params); + connections = new ArrayList<>(); + } + + @AfterEach + public void tearDown() throws Exception { + for (Connection connection : connections) { + try { + connection.close(2000); + } catch (Exception e) { + LOGGER.warn("Error while closing test connection", e); + } + } + + shutdownExecutorService(nioExecutorService); + shutdownExecutorService(connectionShutdownExecutorService); + } + + private void shutdownExecutorService(ExecutorService executorService) throws InterruptedException { + if (executorService == null) { + return; + } + executorService.shutdown(); + boolean terminated = executorService.awaitTermination(5, TimeUnit.SECONDS); + if (!terminated) { + LOGGER.warn("Couldn't terminate executor after 5 seconds"); + } + } + + @Test + public void connectionClosing() throws Exception { + for (int i = 0; i < 10; i++) { + connections.add(cf.newConnection()); + } + closeAllConnectionsAndWaitForRecovery(connections); + for (Connection connection : connections) { + assertTrue(connection.isOpen()); + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/NoAutoRecoveryWhenTcpWindowIsFullTest.java b/src/test/java/com/rabbitmq/client/test/NoAutoRecoveryWhenTcpWindowIsFullTest.java new file mode 100644 index 0000000000..2b0c934cf1 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/NoAutoRecoveryWhenTcpWindowIsFullTest.java @@ -0,0 +1,211 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.DefaultSocketConfigurator; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.Recoverable; +import com.rabbitmq.client.RecoveryListener; +import com.rabbitmq.client.impl.nio.NioParams; +import com.rabbitmq.client.impl.recovery.AutorecoveringChannel; +import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; +import com.rabbitmq.client.test.TestUtils.DisabledIfBrokerRunningOnDocker; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.Socket; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test to trigger and check the fix of rabbitmq/rabbitmq-java-client#341, + * which can be summarized as + * + *
    + *
  • client registers a slow consumer in automatic acknowledgement mode
  • + *
  • there's a fast enough publisher
  • + *
  • the consumer gets flooded with deliveries
  • + *
  • the work pool queue is full, the reading thread is stuck
  • + *
  • more messages come from the network and it fills up the TCP buffer
  • + *
  • the connection is closed by the server due to missed heartbeats but the client doesn't detect it
  • + *
  • a write operation fails because the socket is closed
  • + *
  • connection recovery is never triggered
  • + *
+ * + *

+ * The fix consists in triggering connection recovery when writing + * to the socket fails. + *

+ */ +@DisabledIfBrokerRunningOnDocker +public class NoAutoRecoveryWhenTcpWindowIsFullTest { + + private static final int NUM_MESSAGES_TO_PRODUCE = 50000; + private static final int MESSAGE_PROCESSING_TIME_MS = 3000; + private static final byte[] MESSAGE_CONTENT = ("MESSAGE CONTENT " + NUM_MESSAGES_TO_PRODUCE).getBytes(); + + private ExecutorService executorService; + private AutorecoveringConnection producingConnection; + private AutorecoveringChannel producingChannel; + private AutorecoveringConnection consumingConnection; + private AutorecoveringChannel consumingChannel; + + private CountDownLatch consumerOkLatch; + + @BeforeEach + public void setUp() throws Exception { + // we need several threads to publish, dispatch deliveries, handle RPC responses, etc. + executorService = Executors.newFixedThreadPool(10); + final ConnectionFactory factory = TestUtils.connectionFactory(); + factory.setSocketConfigurator(new DefaultSocketConfigurator() { + + /* default value on Linux */ + int DEFAULT_RECEIVE_BUFFER_SIZE = 43690; + + @Override + public void configure(Socket socket) throws IOException { + super.configure(socket); + socket.setReceiveBufferSize(DEFAULT_RECEIVE_BUFFER_SIZE); + } + }); + factory.setAutomaticRecoveryEnabled(true); + factory.setTopologyRecoveryEnabled(true); + // we try to set the lower values for closing timeouts, etc. + // this makes the test execute faster. + factory.setRequestedHeartbeat(5); + factory.setSharedExecutor(executorService); + // we need the shutdown executor: channel shutting down depends on the work pool, + // which is full. Channel shutting down will time out with the shutdown executor. + factory.setShutdownExecutor(executorService); + factory.setNetworkRecoveryInterval(2000); + + if (TestUtils.USE_NIO) { + factory.setWorkPoolTimeout(10 * 1000); + factory.setNioParams(new NioParams().setWriteQueueCapacity(10 * 1000 * 1000).setNbIoThreads(4)); + } + + producingConnection = (AutorecoveringConnection) factory.newConnection("Producer Connection"); + producingChannel = (AutorecoveringChannel) producingConnection.createChannel(); + consumingConnection = (AutorecoveringConnection) factory.newConnection("Consuming Connection"); + consumingChannel = (AutorecoveringChannel) consumingConnection.createChannel(); + + consumerOkLatch = new CountDownLatch(2); + } + + @AfterEach + public void tearDown() throws IOException { + closeConnectionIfOpen(consumingConnection); + closeConnectionIfOpen(producingConnection); + + executorService.shutdownNow(); + } + + @Test + public void failureAndRecovery() throws IOException, InterruptedException { + final String queue = UUID.randomUUID().toString(); + + final CountDownLatch recoveryLatch = new CountDownLatch(1); + + consumingConnection.addRecoveryListener(new RecoveryListener() { + + @Override + public void handleRecovery(Recoverable recoverable) { + recoveryLatch.countDown(); + } + + @Override + public void handleRecoveryStarted(Recoverable recoverable) { + } + }); + + declareQueue(producingChannel, queue); + produceMessagesInBackground(producingChannel, queue); + startConsumer(queue); + + assertThat(recoveryLatch.await(60, TimeUnit.SECONDS)) + .as("Connection should have been closed and should have recovered by now") + .isTrue(); + + assertThat(consumerOkLatch.await(5, TimeUnit.SECONDS)) + .as("Consumer should have recovered by now") + .isTrue(); + } + + private void closeConnectionIfOpen(Connection connection) throws IOException { + if (connection.isOpen()) { + connection.close(); + } + } + + private void declareQueue(final Channel channel, final String queue) throws IOException { + final Map queueArguments = new HashMap(); + channel.queueDeclare(queue, false, false, false, queueArguments); + } + + private void produceMessagesInBackground(final Channel channel, final String queue) throws IOException { + final AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().deliveryMode(1).build(); + executorService.submit((Callable) () -> { + for (int i = 0; i < NUM_MESSAGES_TO_PRODUCE; i++) { + channel.basicPublish("", queue, false, properties, MESSAGE_CONTENT); + } + closeConnectionIfOpen(producingConnection); + return null; + }); + } + + private void startConsumer(final String queue) throws IOException { + consumingChannel.basicConsume(queue, true, new DefaultConsumer(consumingChannel) { + + @Override + public void handleConsumeOk(String consumerTag) { + consumerOkLatch.countDown(); + } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + consumerWork(); + try { + consumingChannel.basicPublish("", "", null, "".getBytes()); + } catch (Exception e) { + // application should handle writing exceptions + } + } + }); + } + + private void consumerWork() { + try { + Thread.sleep(MESSAGE_PROCESSING_TIME_MS); + } catch (InterruptedException e) { + } + } +} + diff --git a/src/test/java/com/rabbitmq/client/test/PropertyFileInitialisationTest.java b/src/test/java/com/rabbitmq/client/test/PropertyFileInitialisationTest.java new file mode 100644 index 0000000000..57660c17ba --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/PropertyFileInitialisationTest.java @@ -0,0 +1,297 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.ConnectionFactoryConfigurator; +import org.junit.jupiter.api.Test; + +import javax.net.ssl.SSLContext; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; + +import static com.rabbitmq.client.impl.AMQConnection.defaultClientProperties; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * + */ +public class PropertyFileInitialisationTest { + + ConnectionFactory cf = new ConnectionFactory(); + + @Test + public void propertyInitialisationFromFile() throws IOException { + for (String propertyFileLocation : Arrays.asList( + "./src/test/resources/property-file-initialisation/configuration.properties", + "classpath:/property-file-initialisation/configuration.properties")) { + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.load(propertyFileLocation); + checkConnectionFactory(connectionFactory); + } + } + + @Test + public void propertyInitialisationCustomPrefix() throws Exception { + Properties propertiesCustomPrefix = getPropertiesWitPrefix("prefix."); + + cf.load(propertiesCustomPrefix, "prefix."); + checkConnectionFactory(); + } + + @Test + public void propertyInitialisationNoPrefix() throws Exception { + Properties propertiesCustomPrefix = getPropertiesWitPrefix(""); + + cf.load(propertiesCustomPrefix, ""); + checkConnectionFactory(); + } + + @Test + public void propertyInitialisationNullPrefix() throws Exception { + Properties propertiesCustomPrefix = getPropertiesWitPrefix(""); + + cf.load(propertiesCustomPrefix, null); + checkConnectionFactory(); + } + + @Test + public void propertyInitialisationUri() { + cf.load(Collections.singletonMap("rabbitmq.uri", "amqp://foo:bar@127.0.0.1:5673/dummy")); + + assertThat(cf.getUsername()).isEqualTo("foo"); + assertThat(cf.getPassword()).isEqualTo("bar"); + assertThat(cf.getVirtualHost()).isEqualTo("dummy"); + assertThat(cf.getHost()).isEqualTo("127.0.0.1"); + assertThat(cf.getPort()).isEqualTo(5673); + } + + @Test + public void propertyInitialisationIncludeDefaultClientPropertiesByDefault() { + cf.load(new HashMap<>()); + assertThat(cf.getClientProperties().entrySet()).hasSize(defaultClientProperties().size()); + } + + @Test + public void propertyInitialisationAddCustomClientProperty() { + cf.load(new HashMap() {{ + put("rabbitmq.client.properties.foo", "bar"); + }}); + assertThat(cf.getClientProperties().entrySet()).hasSize(defaultClientProperties().size() + 1); + assertThat(cf.getClientProperties()).extracting("foo").isEqualTo("bar"); + } + + @Test + public void propertyInitialisationGetRidOfDefaultClientPropertyWithEmptyValue() { + final String key = defaultClientProperties().entrySet().iterator().next().getKey(); + cf.load(new HashMap() {{ + put("rabbitmq.client.properties." + key, ""); + }}); + assertThat(cf.getClientProperties().entrySet()).hasSize(defaultClientProperties().size() - 1); + } + + @Test + public void propertyInitialisationOverrideDefaultClientProperty() { + final String key = defaultClientProperties().entrySet().iterator().next().getKey(); + cf.load(new HashMap() {{ + put("rabbitmq.client.properties." + key, "whatever"); + }}); + assertThat(cf.getClientProperties().entrySet()).hasSize(defaultClientProperties().size()); + assertThat(cf.getClientProperties()).extracting(key).isEqualTo("whatever"); + } + + @Test + public void propertyInitialisationDoNotUseNio() throws Exception { + cf.load(new HashMap() {{ + put("rabbitmq.use.nio", "false"); + put("rabbitmq.nio.nb.io.threads", "2"); + }}); + assertThat(cf.getNioParams().getNbIoThreads()).isNotEqualTo(2); + } + + @Test + public void lookUp() { + assertThat(ConnectionFactoryConfigurator.lookUp( + ConnectionFactoryConfigurator.SSL_KEY_STORE, + Collections.singletonMap(ConnectionFactoryConfigurator.SSL_KEY_STORE, "some file"), + "" + )).as("exact key should be looked up").isEqualTo("some file"); + + assertThat(ConnectionFactoryConfigurator.lookUp( + ConnectionFactoryConfigurator.SSL_KEY_STORE, + Collections.emptyMap(), + "" + )).as("lookup should return null when no match").isNull(); + + assertThat(ConnectionFactoryConfigurator.lookUp( + ConnectionFactoryConfigurator.SSL_KEY_STORE, + Collections.singletonMap("ssl.key-store", "some file"), // key alias + "" + )).as("alias key should be used when initial is missing").isEqualTo("some file"); + + assertThat(ConnectionFactoryConfigurator.lookUp( + ConnectionFactoryConfigurator.SSL_TRUST_STORE_TYPE, + Collections.emptyMap(), + "", + "JKS" + )).as("default value should be returned when key is not found").isEqualTo("JKS"); + } + + @Test + public void tlsInitialisationWithKeyManagerAndTrustManagerShouldSucceed() { + Stream.of("./src/test/resources/property-file-initialisation/tls/", + "classpath:/property-file-initialisation/tls/").forEach(baseDirectory -> { + Map configuration = new HashMap<>(); + configuration.put(ConnectionFactoryConfigurator.SSL_ENABLED, "true"); + configuration.put(ConnectionFactoryConfigurator.SSL_KEY_STORE, baseDirectory + "keystore.p12"); + configuration.put(ConnectionFactoryConfigurator.SSL_KEY_STORE_PASSWORD, "bunnies"); + configuration.put(ConnectionFactoryConfigurator.SSL_KEY_STORE_TYPE, "PKCS12"); + configuration.put(ConnectionFactoryConfigurator.SSL_KEY_STORE_ALGORITHM, "SunX509"); + + configuration.put(ConnectionFactoryConfigurator.SSL_TRUST_STORE, baseDirectory + "truststore.jks"); + configuration.put(ConnectionFactoryConfigurator.SSL_TRUST_STORE_PASSWORD, "bunnies"); + configuration.put(ConnectionFactoryConfigurator.SSL_TRUST_STORE_TYPE, "JKS"); + configuration.put(ConnectionFactoryConfigurator.SSL_TRUST_STORE_ALGORITHM, "SunX509"); + + configuration.put(ConnectionFactoryConfigurator.SSL_VERIFY_HOSTNAME, "true"); + + ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + ConnectionFactoryConfigurator.load(connectionFactory, configuration, ""); + + verify(connectionFactory, times(1)).useSslProtocol(any(SSLContext.class)); + verify(connectionFactory, times(1)).enableHostnameVerification(); + }); + } + + @Test + public void tlsNotEnabledIfNotConfigured() { + ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + ConnectionFactoryConfigurator.load(connectionFactory, Collections.emptyMap(), ""); + verify(connectionFactory, never()).useSslProtocol(any(SSLContext.class)); + } + + @Test + public void tlsNotEnabledIfDisabled() { + ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + ConnectionFactoryConfigurator.load( + connectionFactory, + Collections.singletonMap(ConnectionFactoryConfigurator.SSL_ENABLED, "false"), + "" + ); + verify(connectionFactory, never()).useSslProtocol(any(SSLContext.class)); + } + + @Test + public void tlsSslContextSetIfTlsEnabled() { + AtomicBoolean sslProtocolSet = new AtomicBoolean(false); + ConnectionFactory connectionFactory = new ConnectionFactory() { + @Override + public ConnectionFactory useSslProtocol(SSLContext context) { + sslProtocolSet.set(true); + return super.useSslProtocol(context); + } + }; + ConnectionFactoryConfigurator.load( + connectionFactory, + Collections.singletonMap(ConnectionFactoryConfigurator.SSL_ENABLED, "true"), + "" + ); + assertThat(sslProtocolSet).isTrue(); + } + + @Test + public void tlsBasicSetupShouldTrustEveryoneWhenServerValidationIsNotEnabled() throws Exception { + String algorithm = ConnectionFactory.computeDefaultTlsProtocol(SSLContext.getDefault().getSupportedSSLParameters().getProtocols()); + Map configuration = new HashMap<>(); + configuration.put(ConnectionFactoryConfigurator.SSL_ENABLED, "true"); + configuration.put(ConnectionFactoryConfigurator.SSL_VALIDATE_SERVER_CERTIFICATE, "false"); + ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + ConnectionFactoryConfigurator.load(connectionFactory, configuration, ""); + verify(connectionFactory, times(1)).useSslProtocol(algorithm); + } + + @Test + public void tlsBasicSetupShouldSetDefaultTrustManagerWhenServerValidationIsEnabled() throws Exception { + Map configuration = new HashMap<>(); + configuration.put(ConnectionFactoryConfigurator.SSL_ENABLED, "true"); + configuration.put(ConnectionFactoryConfigurator.SSL_VALIDATE_SERVER_CERTIFICATE, "true"); + ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + ConnectionFactoryConfigurator.load(connectionFactory, configuration, ""); + verify(connectionFactory, never()).useSslProtocol(anyString()); + verify(connectionFactory, times(1)).useSslProtocol(any(SSLContext.class)); + } + + private void checkConnectionFactory() { + checkConnectionFactory(this.cf); + } + + private void checkConnectionFactory(ConnectionFactory connectionFactory) { + assertThat(connectionFactory.getUsername()).isEqualTo("foo"); + assertThat(connectionFactory.getPassword()).isEqualTo("bar"); + assertThat(connectionFactory.getVirtualHost()).isEqualTo("dummy"); + assertThat(connectionFactory.getHost()).isEqualTo("127.0.0.1"); + assertThat(connectionFactory.getPort()).isEqualTo(5673); + + assertThat(connectionFactory.getRequestedChannelMax()).isEqualTo(1); + assertThat(connectionFactory.getRequestedFrameMax()).isEqualTo(2); + assertThat(connectionFactory.getRequestedHeartbeat()).isEqualTo(10); + assertThat(connectionFactory.getConnectionTimeout()).isEqualTo(10000); + assertThat(connectionFactory.getHandshakeTimeout()).isEqualTo(5000); + + assertThat(connectionFactory.getClientProperties().entrySet()).hasSize(defaultClientProperties().size() + 1); + assertThat(connectionFactory.getClientProperties()).extracting("foo").isEqualTo("bar"); + + assertThat(connectionFactory.isAutomaticRecoveryEnabled()).isFalse(); + assertThat(connectionFactory.isTopologyRecoveryEnabled()).isFalse(); + assertThat(connectionFactory.getNetworkRecoveryInterval()).isEqualTo(10000l); + assertThat(connectionFactory.getChannelRpcTimeout()).isEqualTo(10000); + assertThat(connectionFactory.isChannelShouldCheckRpcResponseType()).isTrue(); + + assertThat(connectionFactory.getNioParams()).isNotNull(); + assertThat(connectionFactory.getNioParams().getReadByteBufferSize()).isEqualTo(32000); + assertThat(connectionFactory.getNioParams().getWriteByteBufferSize()).isEqualTo(32000); + assertThat(connectionFactory.getNioParams().getNbIoThreads()).isEqualTo(2); + assertThat(connectionFactory.getNioParams().getWriteEnqueuingTimeoutInMs()).isEqualTo(5000); + assertThat(connectionFactory.getNioParams().getWriteQueueCapacity()).isEqualTo(1000); + } + + private Properties getPropertiesWitPrefix(String prefix) throws IOException { + Properties properties = new Properties(); + Reader reader = null; + try { + reader = new FileReader("./src/test/resources/property-file-initialisation/configuration.properties"); + properties.load(reader); + } finally { + reader.close(); + } + + Properties propertiesCustomPrefix = new Properties(); + for (Map.Entry entry : properties.entrySet()) { + propertiesCustomPrefix.put( + prefix + entry.getKey().toString().substring(ConnectionFactoryConfigurator.DEFAULT_PREFIX.length()), + entry.getValue() + ); + } + return propertiesCustomPrefix; + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/QueueingConsumerTests.java b/src/test/java/com/rabbitmq/client/test/QueueingConsumerTests.java new file mode 100644 index 0000000000..be689b96fe --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/QueueingConsumerTests.java @@ -0,0 +1,103 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import static org.junit.jupiter.api.Assertions.*; + +import com.rabbitmq.client.ConsumerCancelledException; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.QueueingConsumer; +import com.rabbitmq.client.ShutdownSignalException; + +public class QueueingConsumerTests extends BrokerTestCase{ + static final String QUEUE = "some-queue"; + static final int THREADS = 5; + + @Test public void nThreadShutdown() throws Exception{ + Channel channel = connection.createChannel(); + final QueueingConsumer c = new QueueingConsumer(channel); + channel.queueDeclare(QUEUE, false, true, true, null); + channel.basicConsume(QUEUE, c); + final AtomicInteger count = new AtomicInteger(THREADS); + final CountDownLatch latch = new CountDownLatch(THREADS); + + for(int i = 0; i < THREADS; i++){ + new Thread(){ + @Override public void run(){ + try { + while(true){ + c.nextDelivery(); + } + } catch (ShutdownSignalException sig) { + count.decrementAndGet(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + latch.countDown(); + } + } + }.start(); + } + + connection.close(); + + // Far longer than this could reasonably take + assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertEquals(0, count.get()); + } + + @Test public void consumerCancellationInterruptsQueuingConsumerWait() + throws IOException, InterruptedException { + String queue = "cancel_notification_queue_for_queueing_consumer"; + final BlockingQueue result = new ArrayBlockingQueue(1); + channel.queueDeclare(queue, false, true, false, null); + final QueueingConsumer consumer = new QueueingConsumer(channel); + Runnable receiver = new Runnable() { + + public void run() { + try { + try { + consumer.nextDelivery(); + } catch (ConsumerCancelledException e) { + result.put(true); + return; + } catch (ShutdownSignalException e) { + } catch (InterruptedException e) { + } + result.put(false); + } catch (InterruptedException e) { + fail(); + } + } + }; + Thread t = new Thread(receiver); + t.start(); + channel.basicConsume(queue, consumer); + channel.queueDelete(queue); + assertTrue(result.take()); + t.join(); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/RecoveryAwareAMQConnectionFactoryTest.java b/src/test/java/com/rabbitmq/client/test/RecoveryAwareAMQConnectionFactoryTest.java new file mode 100644 index 0000000000..5b76811274 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/RecoveryAwareAMQConnectionFactoryTest.java @@ -0,0 +1,62 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Address; +import com.rabbitmq.client.AddressResolver; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.MetricsCollector; +import com.rabbitmq.client.impl.ConnectionParams; +import com.rabbitmq.client.impl.FrameHandler; +import com.rabbitmq.client.impl.FrameHandlerFactory; +import com.rabbitmq.client.impl.recovery.RecoveryAwareAMQConnection; +import com.rabbitmq.client.impl.recovery.RecoveryAwareAMQConnectionFactory; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.*; + +public class RecoveryAwareAMQConnectionFactoryTest { + + // see https://github.com/rabbitmq/rabbitmq-java-client/issues/262 + @Test public void tryNextAddressIfTimeoutException() throws IOException, TimeoutException { + final RecoveryAwareAMQConnection connectionThatThrowsTimeout = mock(RecoveryAwareAMQConnection.class); + final RecoveryAwareAMQConnection connectionThatSucceeds = mock(RecoveryAwareAMQConnection.class); + final Queue connections = new ArrayBlockingQueue(10); + connections.add(connectionThatThrowsTimeout); + connections.add(connectionThatSucceeds); + AddressResolver addressResolver = () -> Arrays.asList(new Address("host1"), new Address("host2")); + RecoveryAwareAMQConnectionFactory connectionFactory = new RecoveryAwareAMQConnectionFactory( + new ConnectionParams(), mock(FrameHandlerFactory.class), addressResolver + ) { + @Override + protected RecoveryAwareAMQConnection createConnection(ConnectionParams params, FrameHandler handler, MetricsCollector metricsCollector) { + return connections.poll(); + } + }; + doThrow(TimeoutException.class).when(connectionThatThrowsTimeout).start(); + doNothing().when(connectionThatSucceeds).start(); + Connection returnedConnection = connectionFactory.newConnection(); + assertSame(connectionThatSucceeds, returnedConnection); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/RecoveryDelayHandlerTest.java b/src/test/java/com/rabbitmq/client/test/RecoveryDelayHandlerTest.java new file mode 100644 index 0000000000..e018dd8fc6 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/RecoveryDelayHandlerTest.java @@ -0,0 +1,76 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.Collections; + +import com.rabbitmq.client.RecoveryDelayHandler; +import com.rabbitmq.client.RecoveryDelayHandler.DefaultRecoveryDelayHandler; +import com.rabbitmq.client.RecoveryDelayHandler.ExponentialBackoffDelayHandler; + +import org.junit.jupiter.api.Test; + +public class RecoveryDelayHandlerTest { + + @Test + public void testDefaultRecoveryDelayHandler() { + final RecoveryDelayHandler handler = new DefaultRecoveryDelayHandler(5000); + assertEquals(5000L, handler.getDelay(0)); + assertEquals(5000L, handler.getDelay(1)); + assertEquals(5000L, handler.getDelay(Integer.MAX_VALUE)); + } + + @Test + public void testExponentialBackoffDelayHandlerDefaults() { + final RecoveryDelayHandler handler = new ExponentialBackoffDelayHandler(); + assertEquals(2000L, handler.getDelay(0)); + assertEquals(3000L, handler.getDelay(1)); + assertEquals(5000L, handler.getDelay(2)); + assertEquals(8000L, handler.getDelay(3)); + assertEquals(13000L, handler.getDelay(4)); + assertEquals(21000L, handler.getDelay(5)); + assertEquals(34000L, handler.getDelay(6)); + assertEquals(34000L, handler.getDelay(7)); + assertEquals(34000L, handler.getDelay(8)); + assertEquals(34000L, handler.getDelay(9)); + assertEquals(34000L, handler.getDelay(Integer.MAX_VALUE)); + } + + @Test + public void testExponentialBackoffDelayHandlerSequence() { + final RecoveryDelayHandler handler = new ExponentialBackoffDelayHandler(Arrays.asList(1L, 2L)); + assertEquals(1, handler.getDelay(0)); + assertEquals(2, handler.getDelay(1)); + assertEquals(2, handler.getDelay(2)); + assertEquals(2, handler.getDelay(Integer.MAX_VALUE)); + } + + @Test + public void testExponentialBackoffDelayHandlerWithNullSequence() { + assertThatThrownBy(() -> new ExponentialBackoffDelayHandler(null)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void testExponentialBackoffDelayHandlerWithEmptySequence() { + assertThatThrownBy(() -> new ExponentialBackoffDelayHandler(Collections.emptyList())) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/RefreshCredentialsTest.java b/src/test/java/com/rabbitmq/client/test/RefreshCredentialsTest.java new file mode 100644 index 0000000000..a9901702ba --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/RefreshCredentialsTest.java @@ -0,0 +1,109 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.impl.DefaultCredentialsRefreshService; +import com.rabbitmq.client.impl.RefreshProtectedCredentialsProvider; +import com.rabbitmq.client.test.TestUtils.BrokerVersion; +import com.rabbitmq.client.test.TestUtils.BrokerVersionAtLeast; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +@BrokerVersionAtLeast(BrokerVersion.RABBITMQ_3_8) +public class RefreshCredentialsTest { + + DefaultCredentialsRefreshService refreshService; + + @BeforeEach + public void tearDown() { + if (refreshService != null) { + refreshService.close(); + } + } + + @Test + public void connectionAndRefreshCredentials() throws Exception { + ConnectionFactory cf = TestUtils.connectionFactory(); + CountDownLatch latch = new CountDownLatch(5); + RefreshProtectedCredentialsProvider provider = new RefreshProtectedCredentialsProvider() { + @Override + protected TestToken retrieveToken() { + latch.countDown(); + return new TestToken("guest", 2, Instant.now()); + } + + @Override + protected String usernameFromToken(TestToken token) { + return "guest"; + } + + @Override + protected String passwordFromToken(TestToken token) { + return token.secret; + } + + @Override + protected Duration timeBeforeExpiration(TestToken token) { + return token.getTimeBeforeExpiration(); + } + }; + + cf.setCredentialsProvider(provider); + refreshService = new DefaultCredentialsRefreshService.DefaultCredentialsRefreshServiceBuilder() + .refreshDelayStrategy(DefaultCredentialsRefreshService.fixedDelayBeforeExpirationRefreshDelayStrategy(Duration.ofSeconds(1))) + .approachingExpirationStrategy(expiration -> false) + .build(); + cf.setCredentialsRefreshService(refreshService); + + try (Connection c = cf.newConnection()) { + Channel ch = c.createChannel(); + String queue = ch.queueDeclare().getQueue(); + TestUtils.sendAndConsumeMessage("", queue, queue, c); + assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); + } + } + + private static class TestToken { + + final String secret; + final int expiresIn; + final Instant receivedAt; + + TestToken(String secret, int expiresIn, Instant receivedAt) { + this.secret = secret; + this.expiresIn = expiresIn; + this.receivedAt = receivedAt; + } + + public Duration getTimeBeforeExpiration() { + Instant now = Instant.now(); + long age = receivedAt.until(now, ChronoUnit.SECONDS); + return Duration.ofSeconds(expiresIn - age); + } + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/RequiredPropertiesSuite.java b/src/test/java/com/rabbitmq/client/test/RequiredPropertiesSuite.java new file mode 100644 index 0000000000..f0040e43ab --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/RequiredPropertiesSuite.java @@ -0,0 +1,53 @@ +package com.rabbitmq.client.test; + + +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + */ +public class RequiredPropertiesSuite { //extends Suite { + +/* + private static final Logger LOGGER = LoggerFactory.getLogger(RequiredPropertiesSuite.class); + + public RequiredPropertiesSuite(Class klass, RunnerBuilder builder) throws InitializationError { + super(klass, builder); + } + + public RequiredPropertiesSuite(RunnerBuilder builder, Class[] classes) throws InitializationError { + super(builder, classes); + } + + protected RequiredPropertiesSuite(Class klass, Class[] suiteClasses) throws InitializationError { + super(klass, suiteClasses); + } + + protected RequiredPropertiesSuite(RunnerBuilder builder, Class klass, Class[] suiteClasses) throws InitializationError { + super(builder, klass, suiteClasses); + } + + protected RequiredPropertiesSuite(Class klass, List runners) throws InitializationError { + super(klass, runners); + } + + @Override + protected List getChildren() { + if(!AbstractRMQTestSuite.requiredProperties()) { + return new ArrayList(); + } else { + return super.getChildren(); + } + } + + @Override + protected void runChild(Runner runner, RunNotifier notifier) { + LOGGER.info("Running test {}", runner.getDescription().getDisplayName()); + super.runChild(runner, notifier); + } + + */ +} diff --git a/src/test/java/com/rabbitmq/client/test/RpcTest.java b/src/test/java/com/rabbitmq/client/test/RpcTest.java new file mode 100644 index 0000000000..f600709d19 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/RpcTest.java @@ -0,0 +1,383 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.NetworkConnection; +import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; +import com.rabbitmq.client.impl.recovery.RecordedBinding; +import com.rabbitmq.client.impl.recovery.RecordedConsumer; +import com.rabbitmq.client.impl.recovery.RecordedExchange; +import com.rabbitmq.client.impl.recovery.RecordedQueue; +import com.rabbitmq.client.impl.recovery.TopologyRecoveryFilter; +import com.rabbitmq.tools.Host; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.rabbitmq.client.test.TestUtils.waitAtMost; +import static org.junit.jupiter.api.Assertions.*; + +public class RpcTest { + + Connection clientConnection, serverConnection; + Channel clientChannel, serverChannel; + String queue = "rpc.queue"; + RpcServer rpcServer; + + @BeforeEach + public void init() throws Exception { + clientConnection = TestUtils.connectionFactory().newConnection(); + clientChannel = clientConnection.createChannel(); + serverConnection = TestUtils.connectionFactory().newConnection(); + serverChannel = serverConnection.createChannel(); + serverChannel.queueDeclare(queue, false, false, false, null); + } + + @AfterEach + public void tearDown() throws Exception { + if (rpcServer != null) { + rpcServer.terminateMainloop(); + } + if (serverChannel != null) { + serverChannel.queueDelete(queue); + } + TestUtils.close(clientConnection); + TestUtils.close(serverConnection); + } + + @Test + public void rpc() throws Exception { + rpcServer = new TestRpcServer(serverChannel, queue); + new Thread(() -> { + try { + rpcServer.mainloop(); + } catch (Exception e) { + // safe to ignore when loops ends/server is canceled + } + }).start(); + RpcClient client = new RpcClient(new RpcClientParams() + .channel(clientChannel).exchange("").routingKey(queue).timeout(1000)); + RpcClient.Response response = client.doCall(null, "hello".getBytes()); + assertEquals("*** hello ***", new String(response.getBody())); + assertEquals("pre-hello", response.getProperties().getHeaders().get("pre").toString()); + assertEquals("post-hello", response.getProperties().getHeaders().get("post").toString()); + + Assertions.assertThat(client.getCorrelationId()).isEqualTo(Integer.valueOf(response.getProperties().getCorrelationId())); + + client.close(); + } + + @Test + public void rpcUnroutableShouldTimeout() throws Exception { + rpcServer = new TestRpcServer(serverChannel, queue); + new Thread(() -> { + try { + rpcServer.mainloop(); + } catch (Exception e) { + // safe to ignore when loops ends/server is canceled + } + }).start(); + String noWhereRoutingKey = UUID.randomUUID().toString(); + RpcClient client = new RpcClient(new RpcClientParams() + .channel(clientChannel).exchange("").routingKey(noWhereRoutingKey) + .timeout(500)); + try { + client.primitiveCall("".getBytes()); + fail("Unroutable message, call should have timed out"); + } catch (TimeoutException e) { + // OK + } + client.close(); + } + + @Test + public void rpcUnroutableWithMandatoryFlagShouldThrowUnroutableException() throws Exception { + rpcServer = new TestRpcServer(serverChannel, queue); + new Thread(() -> { + try { + rpcServer.mainloop(); + } catch (Exception e) { + // safe to ignore when loops ends/server is canceled + } + }).start(); + String noWhereRoutingKey = UUID.randomUUID().toString(); + RpcClient client = new RpcClient(new RpcClientParams() + .channel(clientChannel).exchange("").routingKey(noWhereRoutingKey) + .timeout(1000).useMandatory()); + String content = UUID.randomUUID().toString(); + try { + client.primitiveCall(content.getBytes()); + fail("Unroutable message with mandatory enabled, an exception should have been thrown"); + } catch (UnroutableRpcRequestException e) { + assertEquals(noWhereRoutingKey, e.getReturnMessage().getRoutingKey()); + assertEquals(content, new String(e.getReturnMessage().getBody())); + } + try { + client.close(); + } catch (IOException e) { + // OK + } + } + + @Test + public void rpcCustomCorrelationId() throws Exception { + rpcServer = new TestRpcServer(serverChannel, queue); + new Thread(() -> { + try { + rpcServer.mainloop(); + } catch (Exception e) { + // safe to ignore when loops ends/server is canceled + } + }).start(); + RpcClient client = new RpcClient(new RpcClientParams() + .channel(clientChannel).exchange("").routingKey(queue).timeout(1000) + .correlationIdSupplier(RpcClient.incrementingCorrelationIdSupplier("myPrefix-")) + ); + RpcClient.Response response = client.doCall(null, "hello".getBytes()); + Assertions.assertThat(response.getProperties().getCorrelationId()).isEqualTo("myPrefix-1"); + client.close(); + } + + @Test + public void rpcCustomReplyHandler() throws Exception { + rpcServer = new TestRpcServer(serverChannel, queue); + new Thread(() -> { + try { + rpcServer.mainloop(); + } catch (Exception e) { + // safe to ignore when loops ends/server is canceled + } + }).start(); + AtomicInteger replyHandlerCalls = new AtomicInteger(0); + RpcClient client = new RpcClient(new RpcClientParams() + .channel(clientChannel).exchange("").routingKey(queue).timeout(1000) + .replyHandler(reply -> { + replyHandlerCalls.incrementAndGet(); + return RpcClient.DEFAULT_REPLY_HANDLER.apply(reply); + }) + ); + RpcClient.Response response = client.doCall(null, "hello".getBytes()); + assertEquals(1, replyHandlerCalls.get()); + assertEquals("*** hello ***", new String(response.getBody())); + assertEquals("pre-hello", response.getProperties().getHeaders().get("pre").toString()); + assertEquals("post-hello", response.getProperties().getHeaders().get("post").toString()); + client.doCall(null, "hello".getBytes()); + assertEquals(2, replyHandlerCalls.get()); + client.close(); + } + + @Test + public void rpcResponseTimeout() throws Exception { + RpcClient client = new RpcClient(new RpcClientParams() + .channel(clientChannel).exchange("").routingKey(queue)); + try { + client.doCall(null, "hello".getBytes(), 200); + } catch (TimeoutException e) { + // OK + } + assertEquals(0, client.getContinuationMap().size()); + client.close(); + } + + @Test + public void givenConsumerNotRecoveredCanCreateNewClientOnSameChannelAfterConnectionFailure() throws Exception { + // see https://github.com/rabbitmq/rabbitmq-java-client/issues/382 + rpcServer = new TestRpcServer(serverChannel, queue); + new Thread(() -> { + try { + rpcServer.mainloop(); + } catch (Exception e) { + // safe to ignore when loops ends/server is canceled + } + }).start(); + + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.setTopologyRecoveryFilter(new NoDirectReplyToConsumerTopologyRecoveryFilter()); + cf.setNetworkRecoveryInterval(1000); + Connection connection = null; + try { + connection = cf.newConnection(UUID.randomUUID().toString()); + Channel channel = connection.createChannel(); + RpcClient client = new RpcClient(new RpcClientParams() + .channel(channel).exchange("").routingKey(queue).timeout(1000)); + RpcClient.Response response = client.doCall(null, "hello".getBytes()); + assertEquals("*** hello ***", new String(response.getBody())); + final CountDownLatch recoveryLatch = new CountDownLatch(1); + ((AutorecoveringConnection) connection).addRecoveryListener(new RecoveryListener() { + + @Override + public void handleRecovery(Recoverable recoverable) { + recoveryLatch.countDown(); + } + + @Override + public void handleRecoveryStarted(Recoverable recoverable) { + + } + }); + Host.closeConnection((NetworkConnection) connection); + assertTrue(recoveryLatch.await(10, TimeUnit.SECONDS), "Connection should have recovered by now"); + client = new RpcClient(new RpcClientParams() + .channel(channel).exchange("").routingKey(queue).timeout(1000)); + response = client.doCall(null, "hello".getBytes()); + assertEquals("*** hello ***", new String(response.getBody())); + } finally { + if (connection != null) { + connection.close(); + } + } + } + + @Test + public void givenConsumerIsRecoveredCanNotCreateNewClientOnSameChannelAfterConnectionFailure() throws Exception { + // see https://github.com/rabbitmq/rabbitmq-java-client/issues/382 + rpcServer = new TestRpcServer(serverChannel, queue); + new Thread(() -> { + try { + rpcServer.mainloop(); + } catch (Exception e) { + // safe to ignore when loops ends/server is canceled + } + }).start(); + + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.setNetworkRecoveryInterval(1000); + Connection connection = null; + try { + connection = cf.newConnection(UUID.randomUUID().toString()); + Channel channel = connection.createChannel(); + RpcClient client = new RpcClient(new RpcClientParams() + .channel(channel).exchange("").routingKey(queue).timeout(1000)); + RpcClient.Response response = client.doCall(null, "hello".getBytes()); + assertEquals("*** hello ***", new String(response.getBody())); + final CountDownLatch recoveryLatch = new CountDownLatch(1); + ((AutorecoveringConnection) connection).addRecoveryListener(new RecoveryListener() { + + @Override + public void handleRecovery(Recoverable recoverable) { + recoveryLatch.countDown(); + } + + @Override + public void handleRecoveryStarted(Recoverable recoverable) { + + } + }); + Host.closeConnection((NetworkConnection) connection); + assertTrue(recoveryLatch.await(10, TimeUnit.SECONDS), "Connection should have recovered by now"); + try { + new RpcClient(new RpcClientParams() + .channel(channel).exchange("").routingKey(queue).timeout(1000)); + fail("Cannot create RPC client on same channel, an exception should have been thrown"); + } catch (IOException e) { + assertTrue(e.getCause() instanceof ShutdownSignalException); + ShutdownSignalException cause = (ShutdownSignalException) e.getCause(); + assertTrue(cause.getReason() instanceof AMQP.Channel.Close); + assertEquals(406, ((AMQP.Channel.Close) cause.getReason()).getReplyCode()); + } + } finally { + if (connection != null) { + connection.close(); + } + } + } + + @Test public void interruptingServerThreadShouldStopIt() throws Exception { + rpcServer = new TestRpcServer(serverChannel, queue); + Thread serverThread = new Thread(() -> { + try { + rpcServer.mainloop(); + } catch (Exception e) { + // safe to ignore when loops ends/server is canceled + } + }); + serverThread.start(); + RpcClient client = new RpcClient(new RpcClientParams() + .channel(clientChannel).exchange("").routingKey(queue).timeout(1000)); + RpcClient.Response response = client.doCall(null, "hello".getBytes()); + assertEquals("*** hello ***", new String(response.getBody())); + + serverThread.interrupt(); + + waitAtMost(Duration.ofSeconds(1), () -> !serverThread.isAlive()); + + client.close(); + } + + private static class TestRpcServer extends RpcServer { + + public TestRpcServer(Channel channel, String queueName) throws IOException { + super(channel, queueName); + } + + @Override + protected AMQP.BasicProperties preprocessReplyProperties(Delivery request, AMQP.BasicProperties.Builder builder) { + Map headers = new HashMap<>(); + headers.put("pre", "pre-" + new String(request.getBody())); + builder.headers(headers); + return builder.build(); + } + + @Override + public byte[] handleCall(Delivery request, AMQP.BasicProperties replyProperties) { + String input = new String(request.getBody()); + return ("*** " + input + " ***").getBytes(); + } + + @Override + protected AMQP.BasicProperties postprocessReplyProperties(Delivery request, AMQP.BasicProperties.Builder builder) { + Map headers = new HashMap(builder.build().getHeaders()); + headers.put("post", "post-" + new String(request.getBody())); + builder.headers(headers); + return builder.build(); + } + } + + private static class NoDirectReplyToConsumerTopologyRecoveryFilter implements TopologyRecoveryFilter { + + @Override + public boolean filterExchange(RecordedExchange recordedExchange) { + return true; + } + + @Override + public boolean filterQueue(RecordedQueue recordedQueue) { + return true; + } + + @Override + public boolean filterBinding(RecordedBinding recordedBinding) { + return true; + } + + @Override + public boolean filterConsumer(RecordedConsumer recordedConsumer) { + return !"amq.rabbitmq.reply-to".equals(recordedConsumer.getQueue()); + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/RpcTopologyRecordingTest.java b/src/test/java/com/rabbitmq/client/test/RpcTopologyRecordingTest.java new file mode 100644 index 0000000000..6b8828c807 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/RpcTopologyRecordingTest.java @@ -0,0 +1,240 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.AMQImpl; + +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static com.rabbitmq.client.test.TestUtils.closeAndWaitForRecovery; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RpcTopologyRecordingTest extends BrokerTestCase { + + String exchange, queue, routingKey; + String exchange2, queue2, routingKey2; + + public static Object[] data() { + return new Object[]{ + (RpcCall) (channel, method) -> channel.asyncCompletableRpc(method).get(5, TimeUnit.SECONDS), + (RpcCall) (channel, method) -> channel.rpc(method) + }; + } + + @Override + protected ConnectionFactory newConnectionFactory() { + ConnectionFactory connectionFactory = super.newConnectionFactory(); + connectionFactory.setNetworkRecoveryInterval(2); + return connectionFactory; + } + + @Override + protected void createResources() throws IOException, TimeoutException { + super.createResources(); + queue = UUID.randomUUID().toString(); + exchange = UUID.randomUUID().toString(); + routingKey = UUID.randomUUID().toString(); + queue2 = "e2e-" + UUID.randomUUID().toString(); + exchange2 = "e2e-" + UUID.randomUUID().toString(); + routingKey2 = "e2e-" + UUID.randomUUID().toString(); + } + + @Override + protected void releaseResources() throws IOException { + super.releaseResources(); + channel.exchangeDelete(exchange); + channel.exchangeDelete(exchange2); + } + + @ParameterizedTest + @MethodSource("data") + public void topologyRecovery(RpcCall rpcCall) throws Exception { + createTopology(rpcCall); + + AtomicReference latch = new AtomicReference<>(new CountDownLatch(2)); + DeliverCallback countDown = (ctag, message) -> latch.get().countDown(); + channel.basicConsume(queue, countDown, consumerTag -> { + }); + channel.basicConsume(queue2, countDown, consumerTag -> { + }); + + channel.basicPublish(exchange, routingKey, null, "".getBytes()); + channel.basicPublish(exchange, routingKey2, null, "".getBytes()); + + assertTrue(latch.get().await(5, TimeUnit.SECONDS)); + + latch.set(new CountDownLatch(2)); + + closeAndWaitForRecovery((RecoverableConnection) connection); + + channel.basicPublish(exchange, routingKey, null, "".getBytes()); + channel.basicPublish(exchange, routingKey2, null, "".getBytes()); + assertTrue(latch.get().await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @MethodSource("data") + public void deletionAreProperlyRecorded(RpcCall rpcCall) throws Exception { + createTopology(rpcCall); + + AtomicReference latch = new AtomicReference<>(new CountDownLatch(2)); + DeliverCallback countDown = (ctag, message) -> latch.get().countDown(); + String ctag1 = channel.basicConsume(queue, countDown, consumerTag -> { + }); + String ctag2 = channel.basicConsume(queue2, countDown, consumerTag -> { + }); + + channel.basicPublish(exchange, routingKey, null, "".getBytes()); + channel.basicPublish(exchange, routingKey2, null, "".getBytes()); + + assertTrue(latch.get().await(5, TimeUnit.SECONDS)); + + channel.basicCancel(ctag1); + channel.basicCancel(ctag2); + + rpcCall.call(channel, new AMQImpl.Exchange.Delete.Builder().exchange(exchange).build()); + rpcCall.call(channel, new AMQImpl.Exchange.Delete.Builder().exchange(exchange2).build()); + rpcCall.call(channel, new AMQImpl.Queue.Delete.Builder().queue(queue).build()); + rpcCall.call(channel, new AMQImpl.Queue.Delete.Builder().queue(queue2).build()); + + + latch.set(new CountDownLatch(2)); + + closeAndWaitForRecovery((RecoverableConnection) connection); + + assertFalse(queueExists(queue)); + assertFalse(queueExists(queue2)); + assertFalse(exchangeExists(exchange)); + assertFalse(exchangeExists(exchange2)); + } + + boolean queueExists(String queue) throws TimeoutException { + try (Channel ch = connection.createChannel()) { + ch.queueDeclarePassive(queue); + return true; + } catch (IOException e) { + return false; + } + } + + boolean exchangeExists(String exchange) throws TimeoutException { + try (Channel ch = connection.createChannel()) { + ch.exchangeDeclarePassive(exchange); + return true; + } catch (IOException e) { + return false; + } + } + + @ParameterizedTest + @MethodSource("data") + public void bindingDeletionAreProperlyRecorded(RpcCall rpcCall) throws Exception { + createTopology(rpcCall); + + AtomicReference latch = new AtomicReference<>(new CountDownLatch(2)); + DeliverCallback countDown = (ctag, message) -> latch.get().countDown(); + channel.basicConsume(queue, countDown, consumerTag -> { + }); + channel.basicConsume(queue2, countDown, consumerTag -> { + }); + + channel.basicPublish(exchange, routingKey, null, "".getBytes()); + channel.basicPublish(exchange, routingKey2, null, "".getBytes()); + + assertTrue(latch.get().await(5, TimeUnit.SECONDS)); + + unbind(rpcCall); + + latch.set(new CountDownLatch(2)); + + closeAndWaitForRecovery((RecoverableConnection) connection); + + channel.basicPublish(exchange, routingKey, null, "".getBytes()); + channel.basicPublish(exchange, routingKey2, null, "".getBytes()); + assertFalse(latch.get().await(2, TimeUnit.SECONDS)); + } + + private void createTopology(RpcCall rpcCall) throws Exception { + createAndBind(rpcCall, exchange, queue, routingKey); + createAndBind(rpcCall, exchange2, queue2, routingKey2); + rpcCall.call(channel, new AMQImpl.Exchange.Bind.Builder() + .source(exchange) + .destination(exchange2) + .routingKey(routingKey2) + .arguments(null) + .build()); + } + + private void createAndBind(RpcCall rpcCall, String e, String q, String rk) throws Exception { + rpcCall.call(channel, new AMQImpl.Queue.Declare.Builder() + .queue(q) + .durable(false) + .exclusive(true) + .autoDelete(false) + .arguments(null) + .build()); + rpcCall.call(channel, new AMQImpl.Exchange.Declare.Builder() + .exchange(e) + .type("direct") + .durable(false) + .autoDelete(false) + .arguments(null) + .build()); + rpcCall.call(channel, new AMQImpl.Queue.Bind.Builder() + .queue(q) + .exchange(e) + .routingKey(rk) + .arguments(null) + .build()); + } + + private void unbind(RpcCall rpcCall) throws Exception { + rpcCall.call(channel, new AMQImpl.Queue.Unbind.Builder() + .exchange(exchange) + .queue(queue) + .routingKey(routingKey).build() + ); + + rpcCall.call(channel, new AMQImpl.Queue.Unbind.Builder() + .exchange(exchange2) + .queue(queue2) + .routingKey(routingKey2).build() + ); + + rpcCall.call(channel, new AMQImpl.Exchange.Unbind.Builder() + .source(exchange) + .destination(exchange2) + .routingKey(routingKey2).build() + ); + } + + @FunctionalInterface + interface RpcCall { + + void call(Channel channel, Method method) throws Exception; + + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/SharedThreadPoolTest.java b/src/test/java/com/rabbitmq/client/test/SharedThreadPoolTest.java new file mode 100644 index 0000000000..63a875b606 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/SharedThreadPoolTest.java @@ -0,0 +1,82 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.Connection; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.impl.AMQConnection; + +public class SharedThreadPoolTest extends BrokerTestCase { + @Test public void willShutDownExecutor() throws IOException, TimeoutException { + ExecutorService executor1 = null; + ExecutorService executor2 = null; + AMQConnection conn1 = null; + AMQConnection conn2 = null; + AMQConnection conn3 = null; + AMQConnection conn4 = null; + try { + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.setAutomaticRecoveryEnabled(false); + executor1 = Executors.newFixedThreadPool(8); + cf.setSharedExecutor(executor1); + + conn1 = (AMQConnection)cf.newConnection(); + assertFalse(conn1.willShutDownConsumerExecutor()); + + executor2 = Executors.newSingleThreadExecutor(); + conn2 = (AMQConnection)cf.newConnection(executor2); + assertFalse(conn2.willShutDownConsumerExecutor()); + + conn3 = (AMQConnection)cf.newConnection((ExecutorService)null); + assertTrue(conn3.willShutDownConsumerExecutor()); + + cf.setSharedExecutor(null); + + conn4 = (AMQConnection)cf.newConnection(); + assertTrue(conn4.willShutDownConsumerExecutor()); + } finally { + close(conn1); + close(conn2); + close(conn3); + close(conn4); + close(executor1); + close(executor2); + } + + } + + void close(ExecutorService executor) { + if (executor != null) { + executor.shutdownNow(); + } + } + + void close(Connection connection) throws IOException { + if (connection != null) { + connection.close(); + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/SslContextFactoryTest.java b/src/test/java/com/rabbitmq/client/test/SslContextFactoryTest.java new file mode 100644 index 0000000000..f9e798832b --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/SslContextFactoryTest.java @@ -0,0 +1,140 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.SslContextFactory; +import com.rabbitmq.client.TrustEverythingTrustManager; +import org.junit.jupiter.api.Test; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * + */ +public class SslContextFactoryTest { + + @Test public void setSslContextFactory() throws Exception { + doTestSetSslContextFactory(() -> new ConnectionFactory() + .useBlockingIo() + .setAutomaticRecoveryEnabled(true) + ); + doTestSetSslContextFactory(() -> new ConnectionFactory() + .useNio() + .setAutomaticRecoveryEnabled(true) + ); + doTestSetSslContextFactory(() -> new ConnectionFactory() + .useBlockingIo() + .setAutomaticRecoveryEnabled(false) + ); + doTestSetSslContextFactory(() -> new ConnectionFactory() + .useNio() + .setAutomaticRecoveryEnabled(false) + ); + } + + private void doTestSetSslContextFactory(Supplier supplier) throws Exception { + ConnectionFactory connectionFactory = supplier.get(); + SslContextFactory sslContextFactory = sslContextFactory(); + connectionFactory.setSslContextFactory(sslContextFactory); + + Connection connection = connectionFactory.newConnection("connection01"); + TestUtils.close(connection); + try { + connectionFactory.newConnection("connection02"); + fail("The SSL context of this client should not trust the server"); + } catch (SSLHandshakeException e) { + // OK + } + } + + @Test public void socketFactoryTakesPrecedenceOverSslContextFactoryWithBlockingIo() throws Exception { + doTestSocketFactoryTakesPrecedenceOverSslContextFactoryWithBlockingIo(() -> new ConnectionFactory() + .useBlockingIo() + .setAutomaticRecoveryEnabled(true) + ); + doTestSocketFactoryTakesPrecedenceOverSslContextFactoryWithBlockingIo(() -> new ConnectionFactory() + .useBlockingIo() + .setAutomaticRecoveryEnabled(false) + ); + } + + private void doTestSocketFactoryTakesPrecedenceOverSslContextFactoryWithBlockingIo( + Supplier supplier + ) throws Exception { + SslContextFactory sslContextFactory = sslContextFactory(); + SSLContext contextAcceptAll = sslContextFactory.create("connection01"); + ConnectionFactory connectionFactory = supplier.get(); + connectionFactory + .useBlockingIo() + .setSslContextFactory(sslContextFactory) + .setSocketFactory(contextAcceptAll.getSocketFactory()); + + Connection connection = connectionFactory.newConnection("connection01"); + TestUtils.close(connection); + connection = connectionFactory.newConnection("connection02"); + TestUtils.close(connection); + } + + private SslContextFactory sslContextFactory() throws Exception { + SSLContext contextAcceptAll = SSLContext.getInstance(tlsProtocol()); + contextAcceptAll.init(null, new TrustManager[] { new TrustEverythingTrustManager() }, null); + + SSLContext contextRejectAll = SSLContext.getInstance(tlsProtocol()); + contextRejectAll.init(null, new TrustManager[] { new TrustNothingTrustManager() }, null); + + Map sslContexts = new HashMap<>(); + sslContexts.put("connection01", contextAcceptAll); + sslContexts.put("connection02", contextRejectAll); + + SslContextFactory sslContextFactory = name -> sslContexts.get(name); + return sslContextFactory; + } + + private String tlsProtocol() throws NoSuchAlgorithmException { + return ConnectionFactory.computeDefaultTlsProtocol(SSLContext.getDefault().getSupportedSSLParameters().getProtocols()); + } + + private static class TrustNothingTrustManager implements X509TrustManager { + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + throw new CertificateException("Doesn't trust any server"); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/StrictExceptionHandlerTest.java b/src/test/java/com/rabbitmq/client/test/StrictExceptionHandlerTest.java new file mode 100644 index 0000000000..e51c1d42a0 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/StrictExceptionHandlerTest.java @@ -0,0 +1,77 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.Consumer; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.impl.StrictExceptionHandler; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class StrictExceptionHandlerTest { + + @Test + public void tooLongClosingMessage() throws Exception { + ConnectionFactory cf = TestUtils.connectionFactory(); + final CountDownLatch latch = new CountDownLatch(1); + cf.setExceptionHandler(new StrictExceptionHandler() { + + @Override + public void handleConsumerException(Channel channel, Throwable exception, Consumer consumer, String consumerTag, String methodName) { + try { + super.handleConsumerException(channel, exception, consumer, consumerTag, methodName); + } catch (IllegalArgumentException e) { + fail("No exception should caught"); + } + latch.countDown(); + } + }); + try (Connection c = cf.newConnection()) { + Channel channel = c.createChannel(); + String queue = channel.queueDeclare().getQueue(); + channel.basicConsume(queue, + new VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongClassName( + channel + )); + channel.basicPublish("", queue, null, new byte[0]); + assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); + } + } + + static class VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongClassName + extends DefaultConsumer { + + public VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongClassName( + Channel channel) { + super(channel); + } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) { + throw new RuntimeException(); + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/TableTest.java b/src/test/java/com/rabbitmq/client/test/TableTest.java new file mode 100644 index 0000000000..f6019a1405 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/TableTest.java @@ -0,0 +1,93 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.impl.*; +import java.sql.Timestamp; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.*; +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TableTest +{ + + public byte [] marshal(Map table) + throws IOException + { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + MethodArgumentWriter writer = new MethodArgumentWriter(new ValueWriter(new DataOutputStream(buffer))); + writer.writeTable(table); + writer.flush(); + + assertEquals(Frame.tableSize(table) + 4, buffer.size()); + return buffer.toByteArray(); + } + + public Map unmarshal(byte [] bytes) + throws IOException + { + MethodArgumentReader reader = + new MethodArgumentReader + (new ValueReader + (new DataInputStream + (new ByteArrayInputStream(bytes)))); + + return reader.readTable(); + } + + public Date secondDate() + { + return new Date((System.currentTimeMillis()/1000)*1000); + } + + private static Timestamp timestamp() { + return new Timestamp((System.currentTimeMillis()/1000)*1000); + } + + @Test public void loop() + throws IOException + { + Map table = new HashMap<>(); + table.put("a", 1); + assertEquals(table, unmarshal(marshal(table))); + + table.put("b", secondDate()); + assertEquals(table, unmarshal(marshal(table))); + + table.put("c", new BigDecimal("1.1")); + assertEquals(table, unmarshal(marshal(table))); + + table.put("d", LongStringHelper.asLongString("d")); + assertEquals(table, unmarshal(marshal(table))); + + table.put("e", -126); + assertEquals(table, unmarshal(marshal(table))); + + Timestamp timestamp = timestamp(); + table.put("f", timestamp); + Map tableWithTimestampAsDate = new HashMap<>(table); + tableWithTimestampAsDate.put("f", new Date(timestamp.getTime())); + assertEquals(tableWithTimestampAsDate, unmarshal(marshal(table))); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/TestUtils.java b/src/test/java/com/rabbitmq/client/test/TestUtils.java new file mode 100644 index 0000000000..3f2061bffb --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/TestUtils.java @@ -0,0 +1,522 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.NetworkConnection; +import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; +import com.rabbitmq.tools.Host; +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 java.util.function.Function; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.ServerSocket; +import java.time.Duration; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestUtils { + + public static final boolean USE_NIO = System.getProperty("use.nio") != null; + + public static ConnectionFactory connectionFactory() { + ConnectionFactory connectionFactory = new ConnectionFactory(); + if (USE_NIO) { + connectionFactory.useNio(); + } else { + connectionFactory.useBlockingIo(); + } + return connectionFactory; + } + + @FunctionalInterface + public interface CallableBooleanSupplier { + + boolean getAsBoolean() throws Exception; + + } + + public static void waitAtMost(CallableBooleanSupplier condition) { + waitAtMost(Duration.ofSeconds(10), condition); + } + + public static void waitAtMost(Duration timeout, CallableBooleanSupplier condition) { + try { + if (condition.getAsBoolean()) { + return; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + int waitTime = 100; + int waitedTime = 0; + long timeoutInMs = timeout.toMillis(); + while (waitedTime <= timeoutInMs) { + try { + Thread.sleep(waitTime); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + try { + if (condition.getAsBoolean()) { + return; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + waitedTime += waitTime; + } + Assertions.fail("Waited " + timeout.getSeconds() + " second(s), condition never got true"); + } + + public static void close(Connection connection) { + if (connection != null) { + try { + connection.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public static void abort(Connection connection) { + if (connection != null) { + connection.abort(); + } + } + + public static boolean atMost312(Connection connection) { + return atMostVersion("3.12.999", currentVersion(connection.getServerProperties().get("version").toString())); + } + + public static boolean isVersion37orLater(Connection connection) { + return atLeastVersion("3.7.0", connection); + } + + public static boolean isVersion38orLater(Connection connection) { + return atLeastVersion("3.8.0", connection); + } + + public static boolean isVersion310orLater(Connection connection) { + return atLeastVersion("3.10.0", connection); + } + + private static boolean atLeastVersion(String expectedVersion, Connection connection) { + return atLeastVersion(expectedVersion, currentVersion(connection.getServerProperties().get("version").toString())); + } + + private static boolean atLeastVersion(String expectedVersion, String currentVersion) { + try { + return "0.0.0".equals(currentVersion) || versionCompare(currentVersion, expectedVersion) >= 0; + } catch (RuntimeException e) { + LoggerFactory.getLogger(TestUtils.class).warn("Unable to parse broker version {}", currentVersion, e); + throw e; + } + } + + private static boolean atMostVersion(String expectedVersion, String currentVersion) { + try { + return versionCompare(currentVersion, expectedVersion) <= 0; + } catch (RuntimeException e) { + LoggerFactory.getLogger(TestUtils.class).warn("Unable to parse broker version {}", currentVersion, e); + throw e; + } + } + + static String currentVersion(String currentVersion) { + // versions built from source: 3.7.0+rc.1.4.gedc5d96 + if (currentVersion.contains("+")) { + currentVersion = currentVersion.substring(0, currentVersion.indexOf("+")); + } + // alpha (snapshot) versions: 3.7.0~alpha.449-1 + if (currentVersion.contains("~")) { + currentVersion = currentVersion.substring(0, currentVersion.indexOf("~")); + } + // alpha (snapshot) versions: 3.7.1-alpha.40 + if (currentVersion.contains("-")) { + currentVersion = currentVersion.substring(0, currentVersion.indexOf("-")); + } + return currentVersion; + } + + public static boolean sendAndConsumeMessage(String exchange, String routingKey, String queue, Connection c) + throws IOException, TimeoutException, InterruptedException { + Channel ch = c.createChannel(); + try { + ch.confirmSelect(); + final CountDownLatch latch = new CountDownLatch(1); + ch.basicConsume(queue, true, new DefaultConsumer(ch) { + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + latch.countDown(); + } + }); + ch.basicPublish(exchange, routingKey, null, "".getBytes()); + ch.waitForConfirmsOrDie(5000); + return latch.await(5, TimeUnit.SECONDS); + } finally { + if (ch != null && ch.isOpen()) { + ch.close(); + } + } + } + + public static boolean resourceExists(Callable callback) throws Exception { + Channel declarePassiveChannel = null; + try { + declarePassiveChannel = callback.call(); + return true; + } catch (IOException e) { + if (e.getCause() instanceof ShutdownSignalException) { + ShutdownSignalException cause = (ShutdownSignalException) e.getCause(); + if (cause.getReason() instanceof AMQP.Channel.Close) { + if (((AMQP.Channel.Close) cause.getReason()).getReplyCode() == 404) { + return false; + } else { + throw e; + } + } + return false; + } else { + throw e; + } + } finally { + if (declarePassiveChannel != null && declarePassiveChannel.isOpen()) { + declarePassiveChannel.close(); + } + } + } + + public static boolean queueExists(final String queue, final Connection connection) throws Exception { + return resourceExists(() -> { + Channel channel = connection.createChannel(); + channel.queueDeclarePassive(queue); + return channel; + }); + } + + public static boolean exchangeExists(final String exchange, final Connection connection) throws Exception { + return resourceExists(() -> { + Channel channel = connection.createChannel(); + channel.exchangeDeclarePassive(exchange); + return channel; + }); + } + + public static void closeAndWaitForRecovery(RecoverableConnection connection) throws IOException, InterruptedException { + CountDownLatch latch = prepareForRecovery(connection); + Host.closeConnection((NetworkConnection) connection); + wait(latch); + } + + public static void closeAllConnectionsAndWaitForRecovery(Collection connections) throws IOException, InterruptedException { + CountDownLatch latch = prepareForRecovery(connections); + Host.closeAllConnections(); + wait(latch); + } + + public static void closeAllConnectionsAndWaitForRecovery(Connection connection) throws IOException, InterruptedException { + closeAllConnectionsAndWaitForRecovery(Collections.singletonList(connection)); + } + + public static CountDownLatch prepareForRecovery(Connection connection) { + return prepareForRecovery(Collections.singletonList(connection)); + } + + public static CountDownLatch prepareForRecovery(Collection connections) { + final CountDownLatch latch = new CountDownLatch(connections.size()); + for (Connection conn : connections) { + ((AutorecoveringConnection) conn).addRecoveryListener(new RecoveryListener() { + + @Override + public void handleRecovery(Recoverable recoverable) { + latch.countDown(); + } + + @Override + public void handleRecoveryStarted(Recoverable recoverable) { + // No-op + } + }); + } + return latch; + } + + private static void wait(CountDownLatch latch) throws InterruptedException { + assertTrue(latch.await(90, TimeUnit.SECONDS)); + } + + /** + * https://stackoverflow.com/questions/6701948/efficient-way-to-compare-version-strings-in-java + */ + static int versionCompare(String str1, String str2) { + String[] vals1 = str1.split("\\."); + String[] vals2 = str2.split("\\."); + int i = 0; + // set index to first non-equal ordinal or length of shortest version string + while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) { + i++; + } + // compare first non-equal ordinal number + if (i < vals1.length && i < vals2.length) { + int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i])); + return Integer.signum(diff); + } + // the strings are equal or one string is a substring of the other + // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4" + return Integer.signum(vals1.length - vals2.length); + } + + public static int randomNetworkPort() throws IOException { + ServerSocket socket = new ServerSocket(); + socket.bind(null); + int port = socket.getLocalPort(); + socket.close(); + return port; + } + + @FunctionalInterface + public interface CallableFunction { + + R apply(T t) throws Exception; + + } + + public static class LatchConditions { + + static Condition completed() { + return new Condition<>( + countDownLatch-> { + try { + return countDownLatch.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }, + "Latch did not complete in 10 seconds"); + } + + } + + public static boolean basicGetBasicConsume(Connection connection, String queue, final CountDownLatch latch, int msgSize) + throws Exception { + Channel channel = connection.createChannel(); + channel.queueDeclare(queue, false, true, false, null); + channel.queuePurge(queue); + + channel.basicPublish("", queue, null, new byte[msgSize]); + + String tag = channel.basicConsume(queue, false, new DefaultConsumer(channel) { + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + getChannel().basicAck(envelope.getDeliveryTag(), false); + latch.countDown(); + } + }); + + boolean messageReceived = latch.await(20, TimeUnit.SECONDS); + + channel.basicCancel(tag); + + return messageReceived; + } + + /* + public static class DefaultTestSuite extends Suite { + + + public DefaultTestSuite(Class klass, RunnerBuilder builder) + throws InitializationError { + super(klass, builder); + } + + public DefaultTestSuite(RunnerBuilder builder, Class[] classes) + throws InitializationError { + super(builder, classes); + } + + protected DefaultTestSuite(Class klass, Class[] suiteClasses) + throws InitializationError { + super(klass, suiteClasses); + } + + protected DefaultTestSuite(RunnerBuilder builder, Class klass, Class[] suiteClasses) + throws InitializationError { + super(builder, klass, suiteClasses); + } + + @Override + protected void runChild(Runner runner, RunNotifier notifier) { + LOGGER.info("Running test {}", runner.getDescription().getDisplayName()); + super.runChild(runner, notifier); + } + + protected DefaultTestSuite(Class klass, List runners) + throws InitializationError { + super(klass, runners); + } + } + + */ + + public static void safeDelete(Connection connection, String queue) { + try { + Channel ch = connection.createChannel(); + ch.queueDelete(queue); + ch.close(); + } catch (Exception e) { + // OK + } + } + + private static class BaseBrokerVersionAtLeastCondition implements + org.junit.jupiter.api.extension.ExecutionCondition { + + private final Function versionProvider; + + private BaseBrokerVersionAtLeastCondition(Function versionProvider) { + this.versionProvider = versionProvider; + } + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + if (!context.getTestMethod().isPresent()) { + return ConditionEvaluationResult.enabled("Apply only to methods"); + } + String expectedVersion = versionProvider.apply(context); + if (expectedVersion == null) { + return ConditionEvaluationResult.enabled("No broker version requirement"); + } else { + String brokerVersion = + context + .getRoot() + .getStore(Namespace.GLOBAL) + .getOrComputeIfAbsent( + "brokerVersion", + k -> { + try (Connection c = TestUtils.connectionFactory().newConnection()) { + return currentVersion( + c.getServerProperties().get("version").toString() + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + }, + String.class); + + if (atLeastVersion(expectedVersion, brokerVersion)) { + return ConditionEvaluationResult.enabled( + "Broker version requirement met, expected " + + expectedVersion + + ", actual " + + brokerVersion); + } else { + return ConditionEvaluationResult.disabled( + "Broker version requirement not met, expected " + + expectedVersion + + ", actual " + + brokerVersion); + } + } + } + } + + private static class AnnotationBrokerVersionAtLeastCondition + extends BaseBrokerVersionAtLeastCondition { + + private AnnotationBrokerVersionAtLeastCondition() { + super( + context -> { + BrokerVersionAtLeast annotation = + context.getElement().get().getAnnotation(BrokerVersionAtLeast.class); + return annotation == null ? null : annotation.value().toString(); + }); + } + } + + static class BrokerVersionAtLeast310Condition extends BaseBrokerVersionAtLeastCondition { + + private BrokerVersionAtLeast310Condition() { + super(context -> "3.10.0"); + } + } + + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @ExtendWith(AnnotationBrokerVersionAtLeastCondition.class) + public @interface BrokerVersionAtLeast { + + BrokerVersion value(); + } + + public enum BrokerVersion { + RABBITMQ_3_8("3.8.0"), + RABBITMQ_3_10("3.10.0"), + RABBITMQ_4_0("4.0.0"); + + final String value; + + BrokerVersion(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + } + + static class DisabledIfBrokerRunningOnDockerCondition implements + org.junit.jupiter.api.extension.ExecutionCondition { + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + if (Host.isOnDocker()) { + return ConditionEvaluationResult.disabled("Broker running on Docker"); + } else { + return ConditionEvaluationResult.enabled("Broker not running on Docker"); + } + } + } + + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @ExtendWith(DisabledIfBrokerRunningOnDockerCondition.class) + @interface DisabledIfBrokerRunningOnDocker {} + +} diff --git a/src/test/java/com/rabbitmq/client/test/TestUtilsTest.java b/src/test/java/com/rabbitmq/client/test/TestUtilsTest.java new file mode 100644 index 0000000000..296930ad1c --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/TestUtilsTest.java @@ -0,0 +1,64 @@ +// Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.Connection; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TestUtilsTest { + + @Test + public void isVersion37orLater() { + Map serverProperties = new HashMap<>(); + Connection connection = mock(Connection.class); + when(connection.getServerProperties()).thenReturn(serverProperties); + + serverProperties.put("version", "3.7.0+rc.1.4.gedc5d96"); + Assertions.assertThat(TestUtils.isVersion37orLater(connection)).isTrue(); + + serverProperties.put("version", "3.7.0~alpha.449-1"); + Assertions.assertThat(TestUtils.isVersion37orLater(connection)).isTrue(); + + serverProperties.put("version", "3.7.1-alpha.40"); + Assertions.assertThat(TestUtils.isVersion37orLater(connection)).isTrue(); + } + + @Test + public void isVersion38orLater() { + Map serverProperties = new HashMap<>(); + Connection connection = mock(Connection.class); + when(connection.getServerProperties()).thenReturn(serverProperties); + + serverProperties.put("version", "3.7.0+rc.1.4.gedc5d96"); + Assertions.assertThat(TestUtils.isVersion38orLater(connection)).isFalse(); + + serverProperties.put("version", "3.7.0~alpha.449-1"); + Assertions.assertThat(TestUtils.isVersion38orLater(connection)).isFalse(); + + serverProperties.put("version", "3.7.1-alpha.40"); + Assertions.assertThat(TestUtils.isVersion38orLater(connection)).isFalse(); + + serverProperties.put("version", "3.8.0+beta.4.38.g33a7f97"); + Assertions.assertThat(TestUtils.isVersion38orLater(connection)).isTrue(); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/TlsUtilsTest.java b/src/test/java/com/rabbitmq/client/test/TlsUtilsTest.java new file mode 100644 index 0000000000..6fccaffd1f --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/TlsUtilsTest.java @@ -0,0 +1,123 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Arrays; + +import static com.rabbitmq.client.impl.TlsUtils.extensionPrettyPrint; +import static org.assertj.core.api.Assertions.assertThat; + +public class TlsUtilsTest { + + static final byte [] DOES_NOT_MATTER = new byte[0]; + + @Test + public void subjectKeyIdentifier() { + // https://www.alvestrand.no/objectid/2.5.29.14.html + byte[] derOctetString = new byte[]{ + 4, 22, 4, 20, -2, -87, -45, -120, 29, -126, -88, -17, 95, -39, -122, 23, 10, -62, -54, -82, 113, -121, -70, -121 + }; // 04:16:04:14:FE:A9:D3:88:1D:82:A8:EF:5F:D9:86:17:0A:C2:CA:AE:71:87:BA:87 + assertThat(extensionPrettyPrint("2.5.29.14", derOctetString, null)) + .isEqualTo("SubjectKeyIdentifier = FE:A9:D3:88:1D:82:A8:EF:5F:D9:86:17:0A:C2:CA:AE:71:87:BA:87"); + // change the 3rd byte to mimic it's not a octet string, the whole array should be then hex-dumped + derOctetString = new byte[]{ + 4, 22, 3, 20, -2, -87, -45, -120, 29, -126, -88, -17, 95, -39, -122, 23, 10, -62, -54, -82, 113, -121, -70, -121 + }; // 04:16:04:14:FE:A9:D3:88:1D:82:A8:EF:5F:D9:86:17:0A:C2:CA:AE:71:87:BA:87 + assertThat(extensionPrettyPrint("2.5.29.14", derOctetString, null)) + .isEqualTo("SubjectKeyIdentifier = 04:16:03:14:FE:A9:D3:88:1D:82:A8:EF:5F:D9:86:17:0A:C2:CA:AE:71:87:BA:87"); + } + + @Test public void keyUsage() { + // https://www.alvestrand.no/objectid/2.5.29.15.html + // http://javadoc.iaik.tugraz.at/iaik_jce/current/iaik/asn1/BIT_STRING.html + X509Certificate c = Mockito.mock(X509Certificate.class); + Mockito.when(c.getKeyUsage()) + .thenReturn(new boolean[] {true,false,true,false,false,false,false,false,false}) + .thenReturn(new boolean[] {false,false,false,false,false,true,true,false,false}) + .thenReturn(null); + assertThat(extensionPrettyPrint("2.5.29.15", DOES_NOT_MATTER, c)) + .isEqualTo("KeyUsage = digitalSignature/keyEncipherment"); + assertThat(extensionPrettyPrint("2.5.29.15", DOES_NOT_MATTER, c)) + .isEqualTo("KeyUsage = keyCertSign/cRLSign"); + // change the 3rd byte to mimic it's not a bit string, the whole array should be then hex-dumped + byte[] derOctetString = new byte[] { 4, 4, 3, 2, 1, 6}; // 04:04:03:02:01:06 => Certificate Sign, CRL Sign + assertThat(extensionPrettyPrint("2.5.29.15", derOctetString, c)) + .isEqualTo("KeyUsage = 04:04:03:02:01:06"); + } + + @Test public void basicConstraints() { + // https://www.alvestrand.no/objectid/2.5.29.19.html + byte [] derOctetString = new byte [] {0x04, 0x02, 0x30, 0x00}; + assertThat(extensionPrettyPrint("2.5.29.19", derOctetString, null)) + .isEqualTo("BasicConstraints = CA:FALSE"); + derOctetString = new byte [] {4, 5, 48, 3, 1, 1, -1}; // 04:05:30:03:01:01:FF + assertThat(extensionPrettyPrint("2.5.29.19", derOctetString, null)) + .isEqualTo("BasicConstraints = CA:TRUE"); + derOctetString = new byte [] {4, 5, 48, 3, 1, 1, 0}; // 04:05:30:03:01:01:00 + assertThat(extensionPrettyPrint("2.5.29.19", derOctetString, null)) + .isEqualTo("BasicConstraints = CA:FALSE"); + // change the 3rd to mimic it's not what the utils expects, the whole array should be hex-dump + derOctetString = new byte [] {4, 5, 4, 3, 1, 1, 0}; // 04:05:04:03:01:01:00 + assertThat(extensionPrettyPrint("2.5.29.19", derOctetString, null)) + .isEqualTo("BasicConstraints = 04:05:04:03:01:01:00"); + + } + + @Test public void authorityKeyIdentifier() { + // https://www.alvestrand.no/objectid/2.5.29.35.html + byte[] derOctetString = new byte[]{ + 4,24,48,22,-128,20,-5,-46,124,99,-33,127,-44,-92,-114,-102,32,67,-11,-36,117,111,-74,-40,81,111 + }; // 04:18:30:16:80:14:FB:D2:7C:63:DF:7F:D4:A4:8E:9A:20:43:F5:DC:75:6F:B6:D8:51:6F + assertThat(extensionPrettyPrint("2.5.29.35", derOctetString, null)) + .isEqualTo("AuthorityKeyIdentifier = keyid:FB:D2:7C:63:DF:7F:D4:A4:8E:9A:20:43:F5:DC:75:6F:B6:D8:51:6F"); + + // add a byte to mimic not-expected length, the whole array should be hex-dump + derOctetString = new byte[]{ + 4,24,48,22,-128,20,-5,-46,124,99,-33,127,-44,-92,-114,-102,32,67,-11,-36,117,111,-74,-40,81,111, -1 + }; // 04:18:30:16:80:14:FB:D2:7C:63:DF:7F:D4:A4:8E:9A:20:43:F5:DC:75:6F:B6:D8:51:6F + assertThat(extensionPrettyPrint("2.5.29.35", derOctetString, null)) + .isEqualTo("AuthorityKeyIdentifier = 04:18:30:16:80:14:FB:D2:7C:63:DF:7F:D4:A4:8E:9A:20:43:F5:DC:75:6F:B6:D8:51:6F:FF"); + } + + @Test public void extendedKeyUsage() throws CertificateParsingException { + // https://www.alvestrand.no/objectid/2.5.29.37.html + X509Certificate c = Mockito.mock(X509Certificate.class); + Mockito.when(c.getExtendedKeyUsage()) + .thenReturn(Arrays.asList("1.3.6.1.5.5.7.3.1")) + .thenReturn(Arrays.asList("1.3.6.1.5.5.7.3.1", "1.3.6.1.5.5.7.3.2")) + .thenReturn(Arrays.asList("1.3.6.1.5.5.7.3.unknown")) + .thenReturn(null) + .thenThrow(CertificateParsingException.class); + + assertThat(extensionPrettyPrint("2.5.29.37", DOES_NOT_MATTER, c)) + .isEqualTo("ExtendedKeyUsage = TLS Web server authentication"); + assertThat(extensionPrettyPrint("2.5.29.37", DOES_NOT_MATTER, c)) + .isEqualTo("ExtendedKeyUsage = TLS Web server authentication/TLS Web client authentication"); + assertThat(extensionPrettyPrint("2.5.29.37", DOES_NOT_MATTER, c)) + .isEqualTo("ExtendedKeyUsage = 1.3.6.1.5.5.7.3.unknown"); + byte [] derOctetString = new byte[] {0x04, 0x0C, 0x30, 0x0A, 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01}; + assertThat(extensionPrettyPrint("2.5.29.37", derOctetString, c)) + .isEqualTo("ExtendedKeyUsage = 04:0C:30:0A:06:08:2B:06:01:05:05:07:03:01"); + assertThat(extensionPrettyPrint("2.5.29.37", DOES_NOT_MATTER, c)) + .isEqualTo("ExtendedKeyUsage = "); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/TrafficListenerTest.java b/src/test/java/com/rabbitmq/client/test/TrafficListenerTest.java new file mode 100644 index 0000000000..77225c3403 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/TrafficListenerTest.java @@ -0,0 +1,98 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Command; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.TrafficListener; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +public class TrafficListenerTest { + + + static Object[] trafficListenerIsCalled() { + return new Object[] { automaticRecoveryEnabled(), automaticRecoveryDisabled() }; + } + + static Consumer automaticRecoveryEnabled() { + return cf -> cf.setAutomaticRecoveryEnabled(true); + } + + static Consumer automaticRecoveryDisabled() { + return cf -> cf.setAutomaticRecoveryEnabled(false); + } + + @ParameterizedTest + @MethodSource + public void trafficListenerIsCalled(Consumer configurator) throws Exception { + ConnectionFactory cf = TestUtils.connectionFactory(); + TestTrafficListener testTrafficListener = new TestTrafficListener(); + cf.setTrafficListener(testTrafficListener); + configurator.accept(cf); + try (Connection c = cf.newConnection()) { + Channel ch = c.createChannel(); + String queue = ch.queueDeclare().getQueue(); + CountDownLatch latch = new CountDownLatch(1); + ch.basicConsume(queue, true, + (consumerTag, message) -> latch.countDown(), consumerTag -> { + }); + String messageContent = UUID.randomUUID().toString(); + ch.basicPublish("", queue, null, messageContent.getBytes()); + assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertEquals(1, testTrafficListener.outboundContent.size()); + assertEquals(messageContent, testTrafficListener.outboundContent.get(0)); + assertEquals(1, testTrafficListener.inboundContent.size()); + assertEquals(messageContent, testTrafficListener.inboundContent.get(0)); + } + } + + private static class TestTrafficListener implements TrafficListener { + + final List outboundContent = new CopyOnWriteArrayList<>(); + final List inboundContent = new CopyOnWriteArrayList<>(); + + @Override + public void write(Command outboundCommand) { + if (outboundCommand.getMethod() instanceof AMQP.Basic.Publish) { + outboundContent.add(new String(outboundCommand.getContentBody())); + } + } + + @Override + public void read(Command inboundCommand) { + if (inboundCommand.getMethod() instanceof AMQP.Basic.Deliver) { + inboundContent.add(new String(inboundCommand.getContentBody())); + } + } + } +} diff --git a/test/src/com/rabbitmq/client/test/TruncatedInputStreamTest.java b/src/test/java/com/rabbitmq/client/test/TruncatedInputStreamTest.java similarity index 62% rename from test/src/com/rabbitmq/client/test/TruncatedInputStreamTest.java rename to src/test/java/com/rabbitmq/client/test/TruncatedInputStreamTest.java index 72cf4f3b71..2efd433112 100644 --- a/test/src/com/rabbitmq/client/test/TruncatedInputStreamTest.java +++ b/src/test/java/com/rabbitmq/client/test/TruncatedInputStreamTest.java @@ -1,34 +1,35 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test; +import com.rabbitmq.client.impl.TruncatedInputStream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -import com.rabbitmq.client.impl.TruncatedInputStream; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Some basic (retroactive) tests for TruncatedInputStream. */ -public class TruncatedInputStreamTest extends TestCase { +public class TruncatedInputStreamTest { /** a sample truncated stream to run tests on */ private TruncatedInputStream _truncStream; @@ -39,28 +40,20 @@ public class TruncatedInputStreamTest extends TestCase { /** what length to truncate it to */ private static final int TRUNCATED_LENGTH = 3; - @Override protected void setUp() throws Exception { - super.setUp(); + @BeforeEach public void setUp() throws Exception { InputStream baseStream = new ByteArrayInputStream(TEST_BYTES); _truncStream = new TruncatedInputStream(baseStream, TRUNCATED_LENGTH); } - @Override protected void tearDown() throws Exception { + @AfterEach public void tearDown() throws Exception { _truncStream = null; - super.tearDown(); - } - - public static TestSuite suite() { - TestSuite suite = new TestSuite("truncStreams"); - suite.addTestSuite(TruncatedInputStreamTest.class); - return suite; } /** * Check the amount of data initially available is as it should be * @throws IOException if there is an I/O problem */ - public void testAmountInitiallyAvailable() throws IOException { + @Test public void amountInitiallyAvailable() throws IOException { assertEquals(TRUNCATED_LENGTH, _truncStream.available()); } @@ -68,7 +61,7 @@ public void testAmountInitiallyAvailable() throws IOException { * Check the data read from the truncated stream is as it should be * @throws IOException if there is an I/O problem */ - public void testReadTruncatedBytes() throws IOException { + @Test public void readTruncatedBytes() throws IOException { byte[] readBytes = new byte[TEST_BYTES.length]; int numRead = _truncStream.read(readBytes); assertEquals(TRUNCATED_LENGTH, numRead); @@ -82,7 +75,7 @@ public void testReadTruncatedBytes() throws IOException { * @throws IOException * */ - public void testSingleByteReads() throws IOException { + @Test public void singleByteReads() throws IOException { for (int i = 0; i < TRUNCATED_LENGTH; i++) { assertEquals(TEST_BYTES[i], _truncStream.read()); } @@ -96,7 +89,7 @@ public void testSingleByteReads() throws IOException { /** * Check reading a specified number of bytes at an offset gives the right result */ - public void testOffsetMultipleByteReads() throws IOException { + @Test public void offsetMultipleByteReads() throws IOException { byte[] readBytes = new byte[TEST_OFFSET + TEST_LENGTH]; _truncStream.read(readBytes, TEST_OFFSET, TEST_LENGTH); for (int i = 0; i < TEST_OFFSET; i++) { // check the array's initially blank... diff --git a/test/src/com/rabbitmq/client/test/ValueOrExceptionTest.java b/src/test/java/com/rabbitmq/client/test/ValueOrExceptionTest.java similarity index 58% rename from test/src/com/rabbitmq/client/test/ValueOrExceptionTest.java rename to src/test/java/com/rabbitmq/client/test/ValueOrExceptionTest.java index e8a54103f1..bf9a2a4fc8 100644 --- a/test/src/com/rabbitmq/client/test/ValueOrExceptionTest.java +++ b/src/test/java/com/rabbitmq/client/test/ValueOrExceptionTest.java @@ -1,29 +1,28 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -import com.rabbitmq.utility.ValueOrException; import com.rabbitmq.utility.SensibleClone; +import com.rabbitmq.utility.ValueOrException; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; -public class ValueOrExceptionTest extends TestCase { + +public class ValueOrExceptionTest { public static class InsufficientMagicException extends Exception implements SensibleClone { /** Default for no check. */ @@ -38,16 +37,8 @@ public InsufficientMagicException sensibleClone() { } } - - public static TestSuite suite() - { - TestSuite suite = new TestSuite("valueOrEx"); - suite.addTestSuite(ValueOrExceptionTest.class); - return suite; - } - - public void testStoresValue() throws InsufficientMagicException { - Integer value = new Integer(3); + @Test public void storesValue() throws InsufficientMagicException { + Integer value = Integer.valueOf(3); ValueOrException valueOrEx = ValueOrException.makeValue(value); @@ -56,7 +47,7 @@ public void testStoresValue() throws InsufficientMagicException { assertTrue(returnedValue == value); } - public void testClonesException() { + @Test public void clonesException() { InsufficientMagicException exception = new InsufficientMagicException("dummy message"); ValueOrException valueOrEx diff --git a/src/test/java/com/rabbitmq/client/test/functional/AbstractRejectTest.java b/src/test/java/com/rabbitmq/client/test/functional/AbstractRejectTest.java new file mode 100644 index 0000000000..a63db8ef50 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/AbstractRejectTest.java @@ -0,0 +1,81 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.QueueingConsumer; +import com.rabbitmq.client.test.BrokerTestCase; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; + +abstract class AbstractRejectTest extends BrokerTestCase { + + protected Channel secondaryChannel; + + @BeforeEach + @Override + public void setUp(TestInfo info) + throws IOException, TimeoutException { + super.setUp(info); + secondaryChannel = connection.createChannel(); + + } + + @AfterEach + @Override + public void tearDown(TestInfo info) + throws IOException, TimeoutException { + if (secondaryChannel != null) { + secondaryChannel.abort(); + secondaryChannel = null; + } + super.tearDown(info); + } + + protected long checkDelivery(QueueingConsumer.Delivery d, + byte[] msg, boolean redelivered) + { + assertNotNull(d); + return checkDelivery(d.getEnvelope(), d.getBody(), msg, redelivered); + } + + protected long checkDelivery(GetResponse r, byte[] msg, boolean redelivered) + { + assertNotNull(r); + return checkDelivery(r.getEnvelope(), r.getBody(), msg, redelivered); + } + + protected long checkDelivery(Envelope e, byte[] m, + byte[] msg, boolean redelivered) + { + assertNotNull(e); + assertTrue(Arrays.equals(m, msg)); + assertEquals(e.isRedeliver(), redelivered); + return e.getDeliveryTag(); + } +} diff --git a/test/src/com/rabbitmq/client/test/functional/AlternateExchange.java b/src/test/java/com/rabbitmq/client/test/functional/AlternateExchange.java similarity index 79% rename from test/src/com/rabbitmq/client/test/functional/AlternateExchange.java rename to src/test/java/com/rabbitmq/client/test/functional/AlternateExchange.java index 9a33e740d6..2a2c96f6cb 100644 --- a/test/src/com/rabbitmq/client/test/functional/AlternateExchange.java +++ b/src/test/java/com/rabbitmq/client/test/functional/AlternateExchange.java @@ -1,31 +1,37 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.ReturnListener; -import com.rabbitmq.client.GetResponse; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.Map; import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.ReturnListener; +import com.rabbitmq.client.test.BrokerTestCase; +import org.junit.jupiter.api.TestInfo; public class AlternateExchange extends BrokerTestCase { @@ -55,8 +61,9 @@ private static boolean[] expected(String key) { return expected; } - @Override protected void setUp() throws IOException { - super.setUp(); + @BeforeEach + @Override public void setUp(TestInfo info) throws IOException, TimeoutException { + super.setUp(info); channel.addReturnListener(new ReturnListener() { public void handleReturn(int replyCode, String replyText, @@ -77,8 +84,10 @@ public void handleReturn(int replyCode, } @Override protected void releaseResources() throws IOException { - for (String q : resources) { - channel.queueDelete(q); + for (String r : resources) { + channel.queueDelete(r); + // declared by setupRouting + channel.exchangeDelete(r); } } @@ -90,11 +99,12 @@ public void handleReturn(int replyCode, * * @param name the name of the exchange to be created, and queue * to be bound - * @param ae the name of the alternate-exchage + * @param ae the name of the alternate-exchange */ protected void setupRouting(String name, String ae) throws IOException { Map args = new HashMap(); if (ae != null) args.put("alternate-exchange", ae); + channel.exchangeDelete(name); channel.exchangeDeclare(name, "direct", false, false, args); channel.queueBind(name, name, name); } @@ -124,7 +134,7 @@ protected void checkGet(boolean[] expected) throws IOException { for (int i = 0; i < resources.length; i++) { String q = resources[i]; GetResponse r = channel.basicGet(q, true); - assertEquals("check " + q , expected[i], r != null); + assertEquals(expected[i], r != null, "check " + q); } } @@ -174,7 +184,7 @@ protected void check(String key, boolean ret) throws IOException { * check various cases of missing AEs - we expect to see some * warnings in the server logs */ - public void testMissing() throws IOException { + @Test public void missing() throws IOException { setupRouting("x", "u"); check("x", false); //no warning check("u", unrouted, false); //warning @@ -190,7 +200,7 @@ public void testMissing() throws IOException { cleanup(); } - public void testAe() throws IOException { + @Test public void ae() throws IOException { setupRouting(); for (String k : keys) { @@ -203,7 +213,7 @@ public void testAe() throws IOException { cleanup(); } - public void testCycleBreaking() throws IOException { + @Test public void cycleBreaking() throws IOException { setupRouting(); check("z", false); cleanup(); diff --git a/src/test/java/com/rabbitmq/client/test/functional/BasicConsume.java b/src/test/java/com/rabbitmq/client/test/functional/BasicConsume.java new file mode 100644 index 0000000000..38adbf71ee --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/BasicConsume.java @@ -0,0 +1,48 @@ +package com.rabbitmq.client.test.functional; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.test.BrokerTestCase; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +public class BasicConsume extends BrokerTestCase { + + @Test public void basicConsumeOk() throws IOException, InterruptedException { + String q = channel.queueDeclare().getQueue(); + basicPublishPersistent("msg".getBytes("UTF-8"), q); + basicPublishPersistent("msg".getBytes("UTF-8"), q); + + CountDownLatch latch = new CountDownLatch(2); + channel.basicConsume(q, new CountDownLatchConsumer(channel, latch)); + + boolean nbOfExpectedMessagesHasBeenConsumed = latch.await(1, TimeUnit.SECONDS); + assertTrue(nbOfExpectedMessagesHasBeenConsumed, "Not all the messages have been received"); + } + + static class CountDownLatchConsumer extends DefaultConsumer { + + private final CountDownLatch latch; + + public CountDownLatchConsumer(Channel channel, CountDownLatch latch) { + super(channel); + this.latch = latch; + } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + latch.countDown(); + } + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/BasicGet.java b/src/test/java/com/rabbitmq/client/test/functional/BasicGet.java new file mode 100644 index 0000000000..fa7d5b14d6 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/BasicGet.java @@ -0,0 +1,73 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AlreadyClosedException; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.test.BrokerTestCase; + +public class BasicGet extends BrokerTestCase { + @Test public void basicGetWithEnqueuedMessages() throws IOException, InterruptedException { + assertTrue(channel.isOpen()); + String q = channel.queueDeclare().getQueue(); + + basicPublishPersistent("msg".getBytes("UTF-8"), q); + Thread.sleep(250); + + assertNotNull(channel.basicGet(q, true)); + channel.queuePurge(q); + assertNull(channel.basicGet(q, true)); + channel.queueDelete(q); + } + + @Test public void basicGetWithEmptyQueue() throws IOException, InterruptedException { + assertTrue(channel.isOpen()); + String q = channel.queueDeclare().getQueue(); + + assertNull(channel.basicGet(q, true)); + channel.queueDelete(q); + } + + @Test public void basicGetWithClosedChannel() throws IOException, InterruptedException, TimeoutException { + assertTrue(channel.isOpen()); + String q = channel.queueDeclare().getQueue(); + + channel.close(); + assertFalse(channel.isOpen()); + try { + channel.basicGet(q, true); + fail("expected basic.get on a closed channel to fail"); + } catch (AlreadyClosedException e) { + // passed + } finally { + Channel tch = connection.createChannel(); + tch.queueDelete(q); + tch.close(); + } + + } +} diff --git a/test/src/com/rabbitmq/client/test/functional/BindingLifecycle.java b/src/test/java/com/rabbitmq/client/test/functional/BindingLifecycle.java similarity index 73% rename from test/src/com/rabbitmq/client/test/functional/BindingLifecycle.java rename to src/test/java/com/rabbitmq/client/test/functional/BindingLifecycle.java index eb02962f16..746aaa108f 100644 --- a/test/src/com/rabbitmq/client/test/functional/BindingLifecycle.java +++ b/src/test/java/com/rabbitmq/client/test/functional/BindingLifecycle.java @@ -1,23 +1,31 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.GetResponse; @@ -36,16 +44,16 @@ public class BindingLifecycle extends BindingLifecycleBase { /** * This tests that when you purge a queue, all of its messages go. */ - public void testQueuePurge() throws IOException { + @Test public void queuePurge() throws IOException { Binding binding = setupExchangeBindings(false); channel.basicPublish(binding.x, binding.k, null, payload); - // Purge the queue, and test that we don't recieve a message + // Purge the queue, and test that we don't receive a message channel.queuePurge(binding.q); GetResponse response = channel.basicGet(binding.q, true); - assertNull("The response SHOULD BE null", response); + assertNull(response, "The response SHOULD BE null"); deleteExchangeAndQueue(binding); } @@ -57,30 +65,30 @@ public void testQueuePurge() throws IOException { * (Tx-)transacted." */ @SuppressWarnings("deprecation") - public void testUnackedPurge() throws IOException { + @Test public void unackedPurge() throws IOException { Binding binding = setupExchangeBindings(false); channel.basicPublish(binding.x, binding.k, null, payload); GetResponse response = channel.basicGet(binding.q, false); assertFalse(response.getEnvelope().isRedeliver()); - assertNotNull("The response SHOULD NOT BE null", response); + assertNotNull(response, "The response SHOULD NOT BE null"); // If we purge the queue the unacked message should still be there on // recover. channel.queuePurge(binding.q); response = channel.basicGet(binding.q, true); - assertNull("The response SHOULD BE null", response); + assertNull(response, "The response SHOULD BE null"); channel.basicRecover(); response = channel.basicGet(binding.q, false); channel.basicRecover(); assertTrue(response.getEnvelope().isRedeliver()); - assertNotNull("The response SHOULD NOT BE null", response); + assertNotNull(response, "The response SHOULD NOT BE null"); // If we recover then purge the message should go away channel.queuePurge(binding.q); response = channel.basicGet(binding.q, true); - assertNull("The response SHOULD BE null", response); + assertNull(response, "The response SHOULD BE null"); deleteExchangeAndQueue(binding); } @@ -89,7 +97,7 @@ public void testUnackedPurge() throws IOException { * This tests whether when you delete an exchange, that any * bindings attached to it are deleted as well. */ - public void testExchangeDelete() throws IOException { + @Test public void exchangeDelete() throws IOException { boolean durable = true; Binding binding = setupExchangeAndRouteMessage(true); @@ -113,7 +121,7 @@ public void testExchangeDelete() throws IOException { * To test this, you try to delete an exchange with a queue still * bound to it and expect the delete operation to fail. */ - public void testExchangeIfUnused() throws IOException { + @Test public void exchangeIfUnused() throws IOException { boolean durable = true; Binding binding = setupExchangeBindings(true); @@ -145,12 +153,12 @@ public void testExchangeIfUnused() throws IOException { * The unsubscribe should cause the queue to auto_delete, which in * turn should cause the exchange to auto_delete. * - * Then re-declare the queue again and try to rebind it to the same exhange. + * Then re-declare the queue again and try to rebind it to the same exchange. * * Because the exchange has been auto-deleted, the bind operation * should fail. */ - public void testExchangeAutoDelete() throws IOException { + @Test public void exchangeAutoDelete() throws IOException, TimeoutException { doAutoDelete(false, 1); } @@ -161,14 +169,14 @@ public void testExchangeAutoDelete() throws IOException { * The difference should be that the original exchange should not * get auto-deleted */ - public void testExchangeAutoDeleteManyBindings() throws IOException { + @Test public void exchangeAutoDeleteManyBindings() throws IOException, TimeoutException { doAutoDelete(false, 10); } /** * */ - public void testExchangePassiveDeclare() throws IOException { + @Test public void exchangePassiveDeclare() throws IOException { channel.exchangeDeclare("testPassive", "direct"); channel.exchangeDeclarePassive("testPassive"); @@ -184,7 +192,7 @@ public void testExchangePassiveDeclare() throws IOException { /** * Test the behaviour of queue.unbind */ - public void testUnbind() throws Exception { + @Test public void unbind() throws Exception { for (String exchange: new String[]{"amq.fanout", "amq.direct", "amq.topic", "amq.headers"}) { testUnbind(exchange); } diff --git a/test/src/com/rabbitmq/client/test/functional/BindingLifecycleBase.java b/src/test/java/com/rabbitmq/client/test/functional/BindingLifecycleBase.java similarity index 80% rename from test/src/com/rabbitmq/client/test/functional/BindingLifecycleBase.java rename to src/test/java/com/rabbitmq/client/test/functional/BindingLifecycleBase.java index d16c1a59fb..224cb304be 100644 --- a/test/src/com/rabbitmq/client/test/functional/BindingLifecycleBase.java +++ b/src/test/java/com/rabbitmq/client/test/functional/BindingLifecycleBase.java @@ -1,26 +1,31 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + import com.rabbitmq.client.AMQP; import com.rabbitmq.client.GetResponse; import com.rabbitmq.client.QueueingConsumer; -import java.io.IOException; /** * This tests whether bindings are created and nuked properly. @@ -57,7 +62,7 @@ protected void deleteExchangeAndQueue(Binding binding) throws IOException { channel.exchangeDelete(binding.x); } - protected void doAutoDelete(boolean durable, int queues) throws IOException { + protected void doAutoDelete(boolean durable, int queues) throws IOException, TimeoutException { String[] queueNames = null; Binding binding = Binding.randomBinding(); channel.exchangeDeclare(binding.x, "direct", durable, true, null); @@ -109,19 +114,19 @@ protected void doAutoDelete(boolean durable, int queues) throws IOException { } @Override - protected void restart() throws IOException { + protected void restart() throws IOException, TimeoutException { } protected void sendRoutable(Binding binding) throws IOException { channel.basicPublish(binding.x, binding.k, null, payload); GetResponse response = channel.basicGet(binding.q, true); - assertNotNull("The response should not be null", response); + assertNotNull(response, "The response should not be null"); } protected void sendUnroutable(Binding binding) throws IOException { channel.basicPublish(binding.x, binding.k, null, payload); GetResponse response = channel.basicGet(binding.q, true); - assertNull("The response SHOULD BE null", response); + assertNull(response, "The response SHOULD BE null"); } protected Binding setupExchangeAndRouteMessage(boolean durable) throws IOException { @@ -171,7 +176,7 @@ protected Binding(String q, String x, String k) { * Main difference is restarting the broker to make sure that the * durable queues are blasted away. */ - public void testExchangeAutoDeleteDurable() throws IOException { + public void testExchangeAutoDeleteDurable() throws IOException, TimeoutException { doAutoDelete(true, 1); } @@ -179,7 +184,7 @@ public void testExchangeAutoDeleteDurable() throws IOException { * The same thing as testExchangeAutoDeleteManyBindings, but with * durable queues. */ - public void testExchangeAutoDeleteDurableManyBindings() throws IOException { + public void testExchangeAutoDeleteDurableManyBindings() throws IOException, TimeoutException { doAutoDelete(true, 10); } } diff --git a/src/test/java/com/rabbitmq/client/test/functional/CcRoutes.java b/src/test/java/com/rabbitmq/client/test/functional/CcRoutes.java new file mode 100644 index 0000000000..37ae760a12 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/CcRoutes.java @@ -0,0 +1,181 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.AMQP.BasicProperties; +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.test.BrokerTestCase; +import org.junit.jupiter.api.TestInfo; + +public class CcRoutes extends BrokerTestCase { + + private String[] queues; + private final String exDirect = "direct_cc_exchange"; + private final String exTopic = "topic_cc_exchange"; + private BasicProperties.Builder propsBuilder; + protected Map headers; + private List ccList; + private List bccList; + + @BeforeEach + @Override public void setUp(TestInfo info) throws IOException, TimeoutException { + super.setUp(info); + propsBuilder = new BasicProperties.Builder(); + headers = new HashMap<>(); + ccList = new ArrayList<>(); + bccList = new ArrayList<>(); + } + + @Override protected void createResources() throws IOException, TimeoutException { + super.createResources(); + queues = IntStream.range(1, 4) + .mapToObj(index -> CcRoutes.class.getSimpleName() + "." + UUID.randomUUID().toString()) + .collect(Collectors.toList()) + .toArray(new String[]{}); + for (String q : queues) { + channel.queueDeclare(q, false, false, true, null); + } + channel.exchangeDeclare(exDirect, "direct", false, true, null); + channel.exchangeDeclare(exTopic, "topic", false, true, null); + } + + @Override + protected void releaseResources() throws IOException { + super.releaseResources(); + for (String q : queues) { + channel.queueDelete(q); + } + } + + @Test public void ccList() throws IOException { + ccList.add(queue2()); + ccList.add(queue3()); + headerPublish("", queue1(), ccList, null); + expect(new String []{queue1(), queue2(), queue3()}, true); + } + + @Test public void ccIgnoreEmptyAndInvalidRoutes() throws IOException { + bccList.add("frob"); + headerPublish("", queue1(), ccList, bccList); + expect(new String []{queue1()}, true); + } + + @Test public void bcc() throws IOException { + bccList.add(queue2()); + headerPublish("", queue1(), null, bccList); + expect(new String []{queue1(), queue2()}, false); + } + + @Test public void noDuplicates() throws IOException { + ccList.add(queue1()); + ccList.add(queue1()); + bccList.add(queue1()); + headerPublish("", queue1(), ccList, bccList); + expect(new String[] {queue1()}, true); + } + + @Test public void directExchangeWithoutBindings() throws IOException { + ccList.add(queue1()); + headerPublish(exDirect, queue2(), ccList, null); + expect(new String[] {}, true); + } + + @Test public void topicExchange() throws IOException { + ccList.add("routing_key"); + channel.queueBind(queue2(), exTopic, "routing_key"); + headerPublish(exTopic, "", ccList, null); + expect(new String[] {queue2()}, true); + } + + @Test public void boundExchanges() throws IOException { + ccList.add("routing_key1"); + bccList.add("routing_key2"); + channel.exchangeBind(exTopic, exDirect, "routing_key1"); + channel.queueBind(queue2(), exTopic, "routing_key2"); + headerPublish(exDirect, "", ccList, bccList); + expect(new String[] {queue2()}, true); + } + + @Test public void nonArray() throws IOException { + headers.put("CC", 0); + propsBuilder.headers(headers); + channel.basicPublish("", queue1(), propsBuilder.build(), new byte[0]); + try { + expect(new String[] {}, false); + fail(); + } catch (IOException e) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); + } + } + + private void headerPublish(String ex, String to, List cc, List bcc) throws IOException { + if (cc != null) { + headers.put("CC", ccList); + } + if (bcc != null) { + headers.put("BCC", bccList); + } + propsBuilder.headers(headers); + channel.basicPublish(ex, to, propsBuilder.build(), new byte[0]); + } + + private void expect(String[] expectedQueues, boolean usedCc) throws IOException { + GetResponse getResponse; + List expectedList = Arrays.asList(expectedQueues); + for (String q : queues) { + getResponse = basicGet(q); + if (expectedList.contains(q)) { + assertNotNull(getResponse); + assertEquals(0, getResponse.getMessageCount()); + Map headers = getResponse.getProps().getHeaders(); + if (headers != null){ + assertEquals(usedCc, headers.containsKey("CC")); + assertFalse(headers.containsKey("BCC")); + } + } else { + assertNull(getResponse); + } + } + } + + String queue1() { + return queues[0]; + } + + String queue2() { + return queues[1]; + } + + String queue3() { + return queues[2]; + } +} diff --git a/test/src/com/rabbitmq/client/test/functional/ClusteredTestBase.java b/src/test/java/com/rabbitmq/client/test/functional/ClusteredTestBase.java similarity index 72% rename from test/src/com/rabbitmq/client/test/functional/ClusteredTestBase.java rename to src/test/java/com/rabbitmq/client/test/functional/ClusteredTestBase.java index 4f16d182ef..08885f6613 100644 --- a/test/src/com/rabbitmq/client/test/functional/ClusteredTestBase.java +++ b/src/test/java/com/rabbitmq/client/test/functional/ClusteredTestBase.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; @@ -24,6 +23,7 @@ import com.rabbitmq.tools.Host; import java.io.IOException; +import java.util.concurrent.TimeoutException; /** * Base class for tests which would like a second, clustered node. @@ -52,7 +52,7 @@ public void openChannel() throws IOException { private static boolean nonClusteredWarningPrinted; @Override - public void openConnection() throws IOException { + public void openConnection() throws IOException, TimeoutException { super.openConnection(); if (clusteredConnection == null) { try { @@ -97,8 +97,8 @@ private boolean clustered(Connection c1, Connection c2) throws IOException { } ch1.queueDelete(q); - ch1.close(); - ch2.close(); + ch1.abort(); + ch2.abort(); return true; } @@ -124,10 +124,15 @@ public void closeConnection() throws IOException { } protected void stopSecondary() throws IOException { - Host.invokeMakeTarget("stop-secondary-app"); + Host.executeCommand(Host.rabbitmqctlCommand() + + " -n \'" + Host.nodenameB() + "\'" + + " stop_app"); } protected void startSecondary() throws IOException { - Host.invokeMakeTarget("start-secondary-app"); + Host.executeCommand(Host.rabbitmqctlCommand() + + " -n \'" + Host.nodenameB() + "\'" + + " start_app"); + Host.tryConnectFor(10_000, Host.node_portB() == null ? 5673 : Integer.valueOf(Host.node_portB())); } } diff --git a/test/src/com/rabbitmq/client/test/functional/Confirm.java b/src/test/java/com/rabbitmq/client/test/functional/Confirm.java similarity index 66% rename from test/src/com/rabbitmq/client/test/functional/Confirm.java rename to src/test/java/com/rabbitmq/client/test/functional/Confirm.java index 4e9f1a5025..0152ee0a45 100644 --- a/test/src/com/rabbitmq/client/test/functional/Confirm.java +++ b/src/test/java/com/rabbitmq/client/test/functional/Confirm.java @@ -1,22 +1,27 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + + import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.ConfirmListener; @@ -31,6 +36,8 @@ import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.TestInfo; public class Confirm extends BrokerTestCase { @@ -38,11 +45,14 @@ public class Confirm extends BrokerTestCase private static final String TTL_ARG = "x-message-ttl"; + @BeforeEach @Override - protected void setUp() throws IOException { - super.setUp(); + public void setUp(TestInfo info) throws IOException, TimeoutException { + super.setUp(info); channel.confirmSelect(); channel.queueDeclare("confirm-test", true, true, false, null); + channel.queueDeclare("confirm-durable-nonexclusive", true, false, + false, null); channel.basicConsume("confirm-test", true, new DefaultConsumer(channel)); channel.queueDeclare("confirm-test-nondurable", false, true, @@ -60,9 +70,14 @@ protected void setUp() throws IOException { "confirm-multiple-queues"); } - public void testPersistentMandatoryCombinations() - throws IOException, InterruptedException - { + @Override + protected void releaseResources() throws IOException { + super.releaseResources(); + channel.queueDelete("confirm-durable-nonexclusive"); + } + + @Test public void persistentMandatoryCombinations() + throws IOException, InterruptedException, TimeoutException { boolean b[] = { false, true }; for (boolean persistent : b) { for (boolean mandatory : b) { @@ -71,22 +86,19 @@ public void testPersistentMandatoryCombinations() } } - public void testNonDurable() - throws IOException, InterruptedException - { + @Test public void nonDurable() + throws IOException, InterruptedException, TimeoutException { confirmTest("", "confirm-test-nondurable", true, false); } - public void testMandatoryNoRoute() - throws IOException, InterruptedException - { + @Test public void mandatoryNoRoute() + throws IOException, InterruptedException, TimeoutException { confirmTest("", "confirm-test-doesnotexist", false, true); confirmTest("", "confirm-test-doesnotexist", true, true); } - public void testMultipleQueues() - throws IOException, InterruptedException - { + @Test public void multipleQueues() + throws IOException, InterruptedException, TimeoutException { confirmTest("amq.direct", "confirm-multiple-queues", true, false); } @@ -95,52 +107,60 @@ public void testMultipleQueues() * (thus causing a confirm). I'd manually comment out the line in * internal_sync that notifies the clients. */ - public void testQueueDelete() - throws IOException, InterruptedException - { + @Test public void queueDelete() + throws IOException, InterruptedException, TimeoutException { publishN("","confirm-test-noconsumer", true, false); channel.queueDelete("confirm-test-noconsumer"); - channel.waitForConfirmsOrDie(); + channel.waitForConfirmsOrDie(60000); } - public void testQueuePurge() - throws IOException, InterruptedException - { + @Test public void queuePurge() + throws IOException, InterruptedException, TimeoutException { publishN("", "confirm-test-noconsumer", true, false); channel.queuePurge("confirm-test-noconsumer"); - channel.waitForConfirmsOrDie(); + channel.waitForConfirmsOrDie(60000); } - public void testBasicReject() - throws IOException, InterruptedException - { + /* Tests rabbitmq-server #854 */ + @Test public void confirmQueuePurge() + throws IOException, InterruptedException, TimeoutException { + channel.basicQos(1); + for (int i = 0; i < 20000; i++) { + publish("", "confirm-durable-nonexclusive", true, false); + if (i % 100 == 0) { + channel.queuePurge("confirm-durable-nonexclusive"); + } + } + channel.waitForConfirmsOrDie(90000); + } + + @Test public void basicReject() + throws IOException, InterruptedException, TimeoutException { basicRejectCommon(false); - channel.waitForConfirmsOrDie(); + channel.waitForConfirmsOrDie(60000); } - public void testQueueTTL() - throws IOException, InterruptedException - { + @Test public void queueTTL() + throws IOException, InterruptedException, TimeoutException { for (int ttl : new int[]{ 1, 0 }) { Map argMap = Collections.singletonMap(TTL_ARG, (Object)ttl); channel.queueDeclare("confirm-ttl", true, true, false, argMap); publishN("", "confirm-ttl", true, false); - channel.waitForConfirmsOrDie(); + channel.waitForConfirmsOrDie(60000); channel.queueDelete("confirm-ttl"); } } - public void testBasicRejectRequeue() - throws IOException, InterruptedException - { + @Test public void basicRejectRequeue() + throws IOException, InterruptedException, TimeoutException { basicRejectCommon(true); /* wait confirms to go through the broker */ @@ -149,12 +169,11 @@ public void testBasicRejectRequeue() channel.basicConsume("confirm-test-noconsumer", true, new DefaultConsumer(channel)); - channel.waitForConfirmsOrDie(); + channel.waitForConfirmsOrDie(60000); } - public void testBasicRecover() - throws IOException, InterruptedException - { + @Test public void basicRecover() + throws IOException, InterruptedException, TimeoutException { publishN("", "confirm-test-noconsumer", true, false); for (long i = 0; i < NUM_MESSAGES; i++) { @@ -171,10 +190,10 @@ public void testBasicRecover() channel.basicConsume("confirm-test-noconsumer", true, new DefaultConsumer(channel)); - channel.waitForConfirmsOrDie(); + channel.waitForConfirmsOrDie(60000); } - public void testSelect() + @Test public void select() throws IOException { channel.confirmSelect(); @@ -196,9 +215,8 @@ public void testSelect() } } - public void testWaitForConfirms() - throws IOException, InterruptedException - { + @Test public void waitForConfirms() + throws IOException, InterruptedException, TimeoutException { final SortedSet unconfirmedSet = Collections.synchronizedSortedSet(new TreeSet()); channel.addConfirmListener(new ConfirmListener() { @@ -223,31 +241,32 @@ public void handleNack(long seqNo, boolean multiple) { publish("", "confirm-test", true, false); } - channel.waitForConfirmsOrDie(); + channel.waitForConfirmsOrDie(60000); if (!unconfirmedSet.isEmpty()) { fail("waitForConfirms returned with unconfirmed messages"); } } - public void testWaitForConfirmsWithoutConfirmSelected() + @Test public void waitForConfirmsWithoutConfirmSelected() throws IOException, InterruptedException { channel = connection.createChannel(); // Don't enable Confirm mode publish("", "confirm-test", true, false); try { - channel.waitForConfirms(); + channel.waitForConfirms(60000); fail("waitForConfirms without confirms selected succeeded"); - } catch (IllegalStateException _e) {} + } catch (IllegalStateException _e) {} catch (TimeoutException e) { + e.printStackTrace(); + } } - public void testWaitForConfirmsException() - throws IOException, InterruptedException - { + @Test public void waitForConfirmsException() + throws IOException, InterruptedException, TimeoutException { publishN("", "confirm-test", true, false); channel.close(); try { - channel.waitForConfirmsOrDie(); + channel.waitForConfirmsOrDie(60000); fail("waitAcks worked on a closed channel"); } catch (ShutdownSignalException sse) { if (!(sse.getReason() instanceof AMQP.Channel.Close)) @@ -261,11 +280,10 @@ public void testWaitForConfirmsException() /* Publish NUM_MESSAGES messages and wait for confirmations. */ public void confirmTest(String exchange, String queueName, boolean persistent, boolean mandatory) - throws IOException, InterruptedException - { + throws IOException, InterruptedException, TimeoutException { publishN(exchange, queueName, persistent, mandatory); - channel.waitForConfirmsOrDie(); + channel.waitForConfirmsOrDie(60000); } private void publishN(String exchangeName, String queueName, diff --git a/test/src/com/rabbitmq/client/test/functional/ConnectionOpen.java b/src/test/java/com/rabbitmq/client/test/functional/ConnectionOpen.java similarity index 55% rename from test/src/com/rabbitmq/client/test/functional/ConnectionOpen.java rename to src/test/java/com/rabbitmq/client/test/functional/ConnectionOpen.java index 8cbb901bb1..b8fa70a34a 100644 --- a/test/src/com/rabbitmq/client/test/functional/ConnectionOpen.java +++ b/src/test/java/com/rabbitmq/client/test/functional/ConnectionOpen.java @@ -1,61 +1,66 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import java.io.IOException; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.DataInputStream; +import java.io.IOException; import java.net.Socket; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.test.TestUtils; +import org.junit.jupiter.api.Test; import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.MalformedFrameException; import com.rabbitmq.client.Method; -import com.rabbitmq.client.impl.SocketFrameHandler; import com.rabbitmq.client.impl.AMQCommand; -import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.impl.SocketFrameHandler; -import junit.framework.TestCase; +import javax.net.SocketFactory; /** * Check that protocol negotiation works */ -public class ConnectionOpen extends TestCase { - public void testCorrectProtocolHeader() throws IOException { - ConnectionFactory factory = new ConnectionFactory(); - SocketFrameHandler fh = new SocketFrameHandler(factory.getSocketFactory().createSocket("localhost", AMQP.PROTOCOL.PORT)); +public class ConnectionOpen { + @Test public void correctProtocolHeader() throws IOException { + SocketFrameHandler fh = new SocketFrameHandler(SocketFactory.getDefault().createSocket("localhost", AMQP.PROTOCOL.PORT)); fh.sendHeader(); AMQCommand command = new AMQCommand(); while (!command.handleFrame(fh.readFrame())) { } Method m = command.getMethod(); - // System.out.println(m.getClass()); - assertTrue("First command must be Connection.start", - m instanceof AMQP.Connection.Start); + + assertTrue(m instanceof AMQP.Connection.Start, "First command must be Connection.start"); AMQP.Connection.Start start = (AMQP.Connection.Start) m; - assertTrue("Version in Connection.start is <= what we sent", - start.getVersionMajor() < AMQP.PROTOCOL.MAJOR || + assertTrue(start.getVersionMajor() < AMQP.PROTOCOL.MAJOR || (start.getVersionMajor() == AMQP.PROTOCOL.MAJOR && - start.getVersionMinor() <= AMQP.PROTOCOL.MINOR)); + start.getVersionMinor() <= AMQP.PROTOCOL.MINOR), + "Version in Connection.start is <= what we sent"); } - public void testCrazyProtocolHeader() throws IOException { - ConnectionFactory factory = new ConnectionFactory(); + @Test public void crazyProtocolHeader() throws IOException { + ConnectionFactory factory = TestUtils.connectionFactory(); // keep the frame handler's socket - Socket fhSocket = factory.getSocketFactory().createSocket("localhost", AMQP.PROTOCOL.PORT); + Socket fhSocket = SocketFactory.getDefault().createSocket("localhost", AMQP.PROTOCOL.PORT); SocketFrameHandler fh = new SocketFrameHandler(fhSocket); fh.sendHeader(100, 3); // major, minor DataInputStream in = fh.getInputStream(); @@ -84,8 +89,8 @@ public void testCrazyProtocolHeader() throws IOException { } } - public void testFrameMaxLessThanFrameMinSize() throws IOException { - ConnectionFactory factory = new ConnectionFactory(); + @Test public void frameMaxLessThanFrameMinSize() throws IOException, TimeoutException { + ConnectionFactory factory = TestUtils.connectionFactory(); factory.setRequestedFrameMax(100); try { factory.newConnection(); diff --git a/src/test/java/com/rabbitmq/client/test/functional/ConnectionRecovery.java b/src/test/java/com/rabbitmq/client/test/functional/ConnectionRecovery.java new file mode 100644 index 0000000000..5145929eb6 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/ConnectionRecovery.java @@ -0,0 +1,1039 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.AMQP.BasicProperties; +import com.rabbitmq.client.impl.CredentialsProvider; +import com.rabbitmq.client.impl.NetworkConnection; +import com.rabbitmq.client.impl.recovery.*; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.client.test.TestUtils; +import com.rabbitmq.tools.Host; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import java.lang.reflect.Field; +import java.util.*; + +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static com.rabbitmq.client.test.TestUtils.prepareForRecovery; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +@SuppressWarnings("ThrowFromFinallyBlock") +public class ConnectionRecovery extends BrokerTestCase { + private static final long RECOVERY_INTERVAL = 2000; + + private static final int MANY_DECLARATIONS_LOOP_COUNT = 500; + + @Test public void connectionRecovery() throws IOException, InterruptedException { + assertThat(connection.isOpen()).isTrue(); + closeAndWaitForRecovery(); + assertThat(connection.isOpen()).isTrue(); + } + + @Test public void namedConnectionRecovery() + throws IOException, InterruptedException, TimeoutException { + String connectionName = "custom-name"; + RecoverableConnection c = newRecoveringConnection(connectionName); + try { + assertThat(c.isOpen()).isTrue(); + assertThat(connectionName).isEqualTo(c.getClientProvidedName()); + TestUtils.closeAndWaitForRecovery(c); + assertThat(c.isOpen()).isTrue(); + assertThat(connectionName).isEqualTo(c.getClientProvidedName()); + } finally { + c.abort(); + } + } + + @Test public void connectionRecoveryWithServerRestart() throws IOException, InterruptedException { + assertThat(connection.isOpen()).isTrue(); + restartPrimaryAndWaitForRecovery(); + assertThat(connection.isOpen()).isTrue(); + } + + @Test public void connectionRecoveryWithArrayOfAddresses() + throws IOException, InterruptedException, TimeoutException { + final Address[] addresses = {new Address("127.0.0.1"), new Address("127.0.0.1", 5672)}; + RecoverableConnection c = newRecoveringConnection(addresses); + try { + assertThat(c.isOpen()).isTrue(); + TestUtils.closeAndWaitForRecovery(c); + assertThat(c.isOpen()).isTrue(); + } finally { + c.abort(); + } + + } + + @Test public void connectionRecoveryWithListOfAddresses() + throws IOException, InterruptedException, TimeoutException { + + final List
addresses = Arrays.asList(new Address("127.0.0.1"), new Address("127.0.0.1", 5672)); + + RecoverableConnection c = newRecoveringConnection(addresses); + try { + assertThat(c.isOpen()).isTrue(); + TestUtils.closeAndWaitForRecovery(c); + assertThat(c.isOpen()).isTrue(); + } finally { + c.abort(); + } + } + + @Test public void connectionRecoveryWithDisabledTopologyRecovery() + throws IOException, InterruptedException, TimeoutException { + RecoverableConnection c = newRecoveringConnection(true); + Channel ch = c.createChannel(); + String q = "java-client.test.recovery.q2"; + ch.queueDeclare(q, false, true, false, null); + ch.queueDeclarePassive(q); + assertThat(c.isOpen()).isTrue(); + try { + CountDownLatch shutdownLatch = prepareForShutdown(c); + CountDownLatch recoveryLatch = prepareForRecovery(c); + Host.closeConnection((NetworkConnection) c); + wait(shutdownLatch); + wait(recoveryLatch); + assertThat(c.isOpen()).isTrue(); + ch.queueDeclarePassive(q); + fail("expected passive declaration to throw"); + } catch (java.io.IOException e) { + // expected + } finally { + c.abort(); + } + } + + // See https://github.com/rabbitmq/rabbitmq-java-client/pull/350 . + // We want to request fresh creds when recovering. + @Test public void connectionRecoveryRequestsCredentialsAgain() throws Exception { + ConnectionFactory cf = buildConnectionFactoryWithRecoveryEnabled(false); + final String username = cf.getUsername(); + final String password = cf.getPassword(); + final AtomicInteger usernameRequested = new AtomicInteger(0); + final AtomicInteger passwordRequested = new AtomicInteger(0); + cf.setCredentialsProvider(new CredentialsProvider() { + + @Override + public String getUsername() { + usernameRequested.incrementAndGet(); + return username; + } + + @Override + public String getPassword() { + passwordRequested.incrementAndGet(); + return password; + } + }); + RecoverableConnection c = (RecoverableConnection) cf.newConnection(UUID.randomUUID().toString()); + try { + assertThat(c.isOpen()).isTrue(); + assertThat(usernameRequested.get()).isEqualTo(1); + assertThat(passwordRequested.get()).isEqualTo(1); + + TestUtils.closeAndWaitForRecovery(c); + assertThat(c.isOpen()).isTrue(); + // username is requested in AMQConnection#toString, so it can be accessed at any time + assertThat(usernameRequested.get()).isGreaterThanOrEqualTo(2); + assertThat(passwordRequested.get()).isEqualTo(2); + } finally { + c.abort(); + } + } + + // see https://github.com/rabbitmq/rabbitmq-java-client/issues/135 + @Test public void thatShutdownHooksOnConnectionFireBeforeRecoveryStarts() throws IOException, InterruptedException { + final List events = new CopyOnWriteArrayList(); + final CountDownLatch latch = new CountDownLatch(3); // one when started, another when complete + connection.addShutdownListener(cause -> events.add("shutdown hook 1")); + connection.addShutdownListener(cause -> events.add("shutdown hook 2")); + // note: we do not want to expose RecoveryCanBeginListener so this + // test does not use it + final CountDownLatch recoveryCanBeginLatch = new CountDownLatch(1); + ((AutorecoveringConnection)connection).getDelegate().addRecoveryCanBeginListener(new RecoveryCanBeginListener() { + @Override + public void recoveryCanBegin(ShutdownSignalException cause) { + events.add("recovery start hook 1"); + recoveryCanBeginLatch.countDown(); + } + }); + ((RecoverableConnection)connection).addRecoveryListener(new RecoveryListener() { + @Override + public void handleRecovery(Recoverable recoverable) { + latch.countDown(); + } + @Override + public void handleRecoveryStarted(Recoverable recoverable) { + latch.countDown(); + } + @Override + public void handleTopologyRecoveryStarted(Recoverable recoverable) { + latch.countDown(); + } + }); + assertThat(connection.isOpen()).isTrue(); + closeAndWaitForRecovery(); + assertThat(connection.isOpen()).isTrue(); + assertThat(events).element(0).isEqualTo("shutdown hook 1"); + assertThat(events).element(1).isEqualTo("shutdown hook 2"); + recoveryCanBeginLatch.await(5, TimeUnit.SECONDS); + assertThat(events).element(2).isEqualTo("recovery start hook 1"); + connection.close(); + wait(latch); + } + + @Test public void shutdownHooksRecoveryOnConnection() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(2); + connection.addShutdownListener(cause -> latch.countDown()); + assertThat(connection.isOpen()).isTrue(); + closeAndWaitForRecovery(); + assertThat(connection.isOpen()).isTrue(); + connection.close(); + wait(latch); + } + + @Test public void shutdownHooksRecoveryOnChannel() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(3); + channel.addShutdownListener(cause -> latch.countDown()); + assertThat(connection.isOpen()).isTrue(); + closeAndWaitForRecovery(); + assertThat(connection.isOpen()).isTrue(); + closeAndWaitForRecovery(); + assertThat(connection.isOpen()).isTrue(); + connection.close(); + wait(latch); + } + + @Test public void blockedListenerRecovery() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(2); + connection.addBlockedListener(new BlockedListener() { + @Override + public void handleBlocked(String reason) { + latch.countDown(); + } + + @Override + public void handleUnblocked() { + latch.countDown(); + } + }); + closeAndWaitForRecovery(); + block(); + channel.basicPublish("", "", null, "".getBytes()); + unblock(); + wait(latch); + } + + @Test public void channelRecovery() throws IOException, InterruptedException { + Channel ch1 = connection.createChannel(); + Channel ch2 = connection.createChannel(); + + assertThat(ch1.isOpen()).isTrue(); + assertThat(ch2.isOpen()).isTrue(); + closeAndWaitForRecovery(); + expectChannelRecovery(ch1); + expectChannelRecovery(ch2); + } + + @Test public void channelRecoveryWithUserProvidedChannelIDs() throws IOException, InterruptedException { + int n1 = 11; + Channel ch1 = connection.createChannel(n1); + int n2 = 22; + Channel ch2 = connection.createChannel(n2); + + assertThat(ch1.isOpen()).isTrue(); + assertThat(ch2.isOpen()).isTrue(); + closeAndWaitForRecovery(); + expectChannelRecovery(ch1); + expectChannelRecovery(ch2); + + assertThat(ch1.getChannelNumber()).isEqualTo(n1); + assertThat(ch2.getChannelNumber()).isEqualTo(n2); + } + + @Test public void returnListenerRecovery() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + channel.addReturnListener( + (replyCode, replyText, exchange, routingKey, properties, body) -> latch.countDown()); + closeAndWaitForRecovery(); + expectChannelRecovery(channel); + channel.basicPublish("", "unknown", true, false, null, "mandatory1".getBytes()); + wait(latch); + } + + @Test public void confirmListenerRecovery() throws IOException, InterruptedException, TimeoutException { + final CountDownLatch latch = new CountDownLatch(1); + channel.addConfirmListener(new ConfirmListener() { + @Override + public void handleAck(long deliveryTag, boolean multiple) { + latch.countDown(); + } + + @Override + public void handleNack(long deliveryTag, boolean multiple) { + latch.countDown(); + } + }); + String q = channel.queueDeclare(UUID.randomUUID().toString(), false, false, false, null).getQueue(); + closeAndWaitForRecovery(); + expectChannelRecovery(channel); + channel.confirmSelect(); + basicPublishVolatile(q); + waitForConfirms(channel); + wait(latch); + } + + @Test public void exchangeRecovery() throws IOException, InterruptedException, TimeoutException { + Channel ch = connection.createChannel(); + String x = "java-client.test.recovery.x1"; + declareExchange(ch, x); + closeAndWaitForRecovery(); + expectChannelRecovery(ch); + expectExchangeRecovery(ch, x); + ch.exchangeDelete(x); + } + + @Test public void exchangeRecoveryWithNoWait() throws IOException, InterruptedException, TimeoutException { + Channel ch = connection.createChannel(); + String x = "java-client.test.recovery.x1-nowait"; + declareExchangeNoWait(ch, x); + closeAndWaitForRecovery(); + expectChannelRecovery(ch); + expectExchangeRecovery(ch, x); + ch.exchangeDelete(x); + } + + @Test public void clientNamedQueueRecovery() throws IOException, InterruptedException, TimeoutException { + testClientNamedQueueRecoveryWith("java-client.test.recovery.q1", false); + } + + @Test public void clientNamedQueueRecoveryWithNoWait() throws IOException, InterruptedException, TimeoutException { + testClientNamedQueueRecoveryWith("java-client.test.recovery.q1-nowait", true); + } + + private void testClientNamedQueueRecoveryWith(String q, boolean noWait) throws IOException, InterruptedException, TimeoutException { + Channel ch = connection.createChannel(); + if(noWait) { + declareClientNamedQueueNoWait(ch, q); + } else { + declareClientNamedQueue(ch, q); + } + closeAndWaitForRecovery(); + expectChannelRecovery(ch); + expectQueueRecovery(ch, q); + ch.queueDelete(q); + } + + @Test public void clientNamedQueueBindingRecovery() throws IOException, InterruptedException, TimeoutException { + String q = "java-client.test.recovery.q2"; + String x = "tmp-fanout"; + Channel ch = connection.createChannel(); + ch.queueDelete(q); + ch.exchangeDelete(x); + ch.exchangeDeclare(x, "fanout"); + declareClientNamedAutoDeleteQueue(ch, q); + ch.queueBind(q, x, ""); + closeAndWaitForRecovery(); + expectChannelRecovery(ch); + expectAutoDeleteQueueAndBindingRecovery(ch, x, q); + ch.queueDelete(q); + ch.exchangeDelete(x); + } + + // bug 26552 + @Test public void clientNamedTransientAutoDeleteQueueAndBindingRecovery() throws IOException, InterruptedException, TimeoutException { + String q = UUID.randomUUID().toString(); + String x = "tmp-fanout"; + Channel ch = connection.createChannel(); + ch.queueDelete(q); + ch.exchangeDelete(x); + ch.exchangeDeclare(x, "fanout"); + ch.queueDeclare(q, false, false, true, null); + ch.queueBind(q, x, ""); + restartPrimaryAndWaitForRecovery(); + expectChannelRecovery(ch); + ch.confirmSelect(); + ch.queuePurge(q); + ch.exchangeDeclare(x, "fanout"); + ch.basicPublish(x, "", null, "msg".getBytes()); + waitForConfirms(ch); + AMQP.Queue.DeclareOk ok = ch.queueDeclare(q, false, false, true, null); + assertThat(ok.getMessageCount()).isEqualTo(1); + ch.queueDelete(q); + ch.exchangeDelete(x); + } + + // bug 26552 + @Test public void serverNamedTransientAutoDeleteQueueAndBindingRecovery() throws IOException, InterruptedException, TimeoutException { + String x = "tmp-fanout"; + Channel ch = connection.createChannel(); + ch.exchangeDelete(x); + ch.exchangeDeclare(x, "fanout"); + String q = ch.queueDeclare("", false, false, true, null).getQueue(); + final AtomicReference nameBefore = new AtomicReference(q); + final AtomicReference nameAfter = new AtomicReference(); + final CountDownLatch listenerLatch = new CountDownLatch(1); + ((AutorecoveringConnection)connection).addQueueRecoveryListener((oldName, newName) -> { + nameBefore.set(oldName); + nameAfter.set(newName); + listenerLatch.countDown(); + }); + ch.queueBind(nameBefore.get(), x, ""); + restartPrimaryAndWaitForRecovery(); + expectChannelRecovery(ch); + ch.confirmSelect(); + ch.exchangeDeclare(x, "fanout"); + ch.basicPublish(x, "", null, "msg".getBytes()); + waitForConfirms(ch); + AMQP.Queue.DeclareOk ok = ch.queueDeclarePassive(nameAfter.get()); + assertThat(ok.getMessageCount()).isEqualTo(1); + ch.queueDelete(nameAfter.get()); + ch.exchangeDelete(x); + } + + @Test public void declarationOfManyAutoDeleteQueuesWithTransientConsumer() throws IOException, TimeoutException { + Channel ch = connection.createChannel(); + assertRecordedQueues(connection, 0); + for(int i = 0; i < MANY_DECLARATIONS_LOOP_COUNT; i++) { + String q = UUID.randomUUID().toString(); + ch.queueDeclare(q, false, false, true, null); + DefaultConsumer dummy = new DefaultConsumer(ch); + String tag = ch.basicConsume(q, true, dummy); + ch.basicCancel(tag); + } + assertRecordedQueues(connection, 0); + ch.close(); + } + + @Test public void declarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreUnbound() throws IOException, TimeoutException { + Channel ch = connection.createChannel(); + assertRecordedExchanges(connection, 0); + for(int i = 0; i < MANY_DECLARATIONS_LOOP_COUNT; i++) { + String x = UUID.randomUUID().toString(); + ch.exchangeDeclare(x, "fanout", false, true, null); + String q = ch.queueDeclare().getQueue(); + final String rk = "doesn't matter"; + ch.queueBind(q, x, rk); + ch.queueUnbind(q, x, rk); + ch.queueDelete(q); + } + assertRecordedExchanges(connection, 0); + ch.close(); + } + + @Test public void declarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreDeleted() throws IOException, TimeoutException { + Channel ch = connection.createChannel(); + assertRecordedExchanges(connection, 0); + for(int i = 0; i < MANY_DECLARATIONS_LOOP_COUNT; i++) { + String x = UUID.randomUUID().toString(); + ch.exchangeDeclare(x, "fanout", false, true, null); + String q = ch.queueDeclare().getQueue(); + ch.queueBind(q, x, "doesn't matter"); + ch.queueDelete(q); + } + assertRecordedExchanges(connection, 0); + ch.close(); + } + + @Test public void declarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreUnbound() throws IOException, TimeoutException { + Channel ch = connection.createChannel(); + assertRecordedExchanges(connection, 0); + for(int i = 0; i < MANY_DECLARATIONS_LOOP_COUNT; i++) { + String src = "src-" + UUID.randomUUID().toString(); + String dest = "dest-" + UUID.randomUUID().toString(); + ch.exchangeDeclare(src, "fanout", false, true, null); + ch.exchangeDeclare(dest, "fanout", false, true, null); + final String rk = "doesn't matter"; + ch.exchangeBind(dest, src, rk); + ch.exchangeUnbind(dest, src, rk); + ch.exchangeDelete(dest); + } + assertRecordedExchanges(connection, 0); + ch.close(); + } + + @Test public void declarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreDeleted() throws IOException, TimeoutException { + Channel ch = connection.createChannel(); + assertRecordedExchanges(connection, 0); + for(int i = 0; i < MANY_DECLARATIONS_LOOP_COUNT; i++) { + String src = "src-" + UUID.randomUUID().toString(); + String dest = "dest-" + UUID.randomUUID().toString(); + ch.exchangeDeclare(src, "fanout", false, true, null); + ch.exchangeDeclare(dest, "fanout", false, true, null); + ch.exchangeBind(dest, src, "doesn't matter"); + ch.exchangeDelete(dest); + } + assertRecordedExchanges(connection, 0); + ch.close(); + } + + @Test public void serverNamedQueueRecovery() throws IOException, InterruptedException { + String q = channel.queueDeclare("", false, false, false, null).getQueue(); + String x = "amq.fanout"; + channel.queueBind(q, x, ""); + + final AtomicReference nameBefore = new AtomicReference(); + final AtomicReference nameAfter = new AtomicReference(); + final CountDownLatch listenerLatch = new CountDownLatch(1); + ((AutorecoveringConnection)connection).addQueueRecoveryListener((oldName, newName) -> { + nameBefore.set(oldName); + nameAfter.set(newName); + listenerLatch.countDown(); + }); + + closeAndWaitForRecovery(); + wait(listenerLatch); + expectChannelRecovery(channel); + channel.basicPublish(x, "", null, "msg".getBytes()); + assertDelivered(q, 1); + assertThat(nameBefore).doesNotHaveValue(nameAfter.get()); + channel.queueDelete(q); + } + + @Test public void exchangeToExchangeBindingRecovery() throws IOException, InterruptedException { + String q = channel.queueDeclare("", false, false, false, null).getQueue(); + String x1 = "amq.fanout"; + String x2 = generateExchangeName(); + channel.exchangeDeclare(x2, "fanout"); + channel.exchangeBind(x1, x2, ""); + channel.queueBind(q, x1, ""); + + try { + closeAndWaitForRecovery(); + expectChannelRecovery(channel); + channel.basicPublish(x2, "", null, "msg".getBytes()); + assertDelivered(q, 1); + } finally { + channel.exchangeDelete(x2); + channel.queueDelete(q); + } + } + + @Test public void thatDeletedQueueBindingsDontReappearOnRecovery() throws IOException, InterruptedException { + String q = channel.queueDeclare("", false, false, false, null).getQueue(); + String x1 = "amq.fanout"; + String x2 = generateExchangeName(); + channel.exchangeDeclare(x2, "fanout"); + channel.exchangeBind(x1, x2, ""); + channel.queueBind(q, x1, ""); + channel.queueUnbind(q, x1, ""); + + try { + closeAndWaitForRecovery(); + expectChannelRecovery(channel); + channel.basicPublish(x2, "", null, "msg".getBytes()); + assertDelivered(q, 0); + } finally { + channel.exchangeDelete(x2); + channel.queueDelete(q); + } + } + + @Test public void thatDeletedExchangeBindingsDontReappearOnRecovery() throws IOException, InterruptedException { + String q = channel.queueDeclare("", false, false, false, null).getQueue(); + String x1 = "amq.fanout"; + String x2 = generateExchangeName(); + channel.exchangeDeclare(x2, "fanout"); + channel.exchangeBind(x1, x2, ""); + channel.queueBind(q, x1, ""); + channel.exchangeUnbind(x1, x2, ""); + + try { + closeAndWaitForRecovery(); + expectChannelRecovery(channel); + channel.basicPublish(x2, "", null, "msg".getBytes()); + assertDelivered(q, 0); + } finally { + channel.exchangeDelete(x2); + channel.queueDelete(q); + } + } + + @Test public void thatDeletedExchangeDoesNotReappearOnRecover() throws IOException, InterruptedException { + String x = generateExchangeName(); + channel.exchangeDeclare(x, "fanout"); + channel.exchangeDelete(x); + try { + closeAndWaitForRecovery(); + expectChannelRecovery(channel); + channel.exchangeDeclarePassive(x); + fail("Expected passive declare to fail"); + } catch (IOException ioe) { + // expected + } + } + + @Test public void thatDeletedQueueDoesNotReappearOnRecover() throws IOException, InterruptedException { + String q = channel.queueDeclare().getQueue(); + channel.queueDelete(q); + try { + closeAndWaitForRecovery(); + expectChannelRecovery(channel); + channel.queueDeclarePassive(q); + fail("Expected passive declare to fail"); + } catch (IOException ioe) { + // expected + } + } + + @Test public void thatExcludedQueueDoesNotReappearOnRecover() throws IOException, InterruptedException { + final String q = "java-client.test.recovery.excludedQueue1"; + channel.queueDeclare(q, true, false, false, null); + // now delete it using the delegate so AutorecoveringConnection and AutorecoveringChannel are not aware of it + ((AutorecoveringChannel)channel).getDelegate().queueDelete(q); + assertThat(((AutorecoveringConnection)connection).getRecordedQueues().get(q)).isNotNull(); + // exclude the queue from recovery + ((AutorecoveringConnection)connection).excludeQueueFromRecovery(q, true); + // verify its not there + assertThat(((AutorecoveringConnection)connection).getRecordedQueues().get(q)).isNull(); + // reconnect + closeAndWaitForRecovery(); + expectChannelRecovery(channel); + // verify queue was not recreated + try { + channel.queueDeclarePassive(q); + fail("Expected passive declare to fail"); + } catch (IOException ioe) { + // expected + } + } + + @Test public void thatCancelledConsumerDoesNotReappearOnRecover() throws IOException, InterruptedException { + String q = UUID.randomUUID().toString(); + channel.queueDeclare(q, false, false, false, null); + String tag = channel.basicConsume(q, new DefaultConsumer(channel)); + assertConsumerCount(1, q); + channel.basicCancel(tag); + closeAndWaitForRecovery(); + expectChannelRecovery(channel); + assertConsumerCount(0, q); + } + + @Test public void consumerRecoveryWithManyConsumers() throws IOException, InterruptedException { + String q = channel.queueDeclare(UUID.randomUUID().toString(), false, false, false, null).getQueue(); + final int n = 1024; + for (int i = 0; i < n; i++) { + channel.basicConsume(q, new DefaultConsumer(channel)); + } + final AtomicReference tagA = new AtomicReference(); + final AtomicReference tagB = new AtomicReference(); + final CountDownLatch listenerLatch = new CountDownLatch(n); + ((AutorecoveringConnection)connection).addConsumerRecoveryListener( + (oldConsumerTag, newConsumerTag) -> { + tagA.set(oldConsumerTag); + tagB.set(newConsumerTag); + listenerLatch.countDown(); + }); + + assertConsumerCount(n, q); + closeAndWaitForRecovery(); + wait(listenerLatch); + assertThat(tagA.get().equals(tagB.get())).isTrue(); + expectChannelRecovery(channel); + assertConsumerCount(n, q); + + } + + @Test public void subsequentRecoveriesWithClientNamedQueue() throws IOException, InterruptedException { + String q = channel.queueDeclare(UUID.randomUUID().toString(), false, false, false, null).getQueue(); + + assertConsumerCount(0, q); + channel.basicConsume(q, new DefaultConsumer(channel)); + + for(int i = 0; i < 10; i++) { + assertConsumerCount(1, q); + closeAndWaitForRecovery(); + } + + channel.queueDelete(q); + } + + @Test public void queueRecoveryWithManyQueues() throws IOException, InterruptedException, TimeoutException { + List qs = new ArrayList(); + final int n = 1024; + for (int i = 0; i < n; i++) { + qs.add(channel.queueDeclare(UUID.randomUUID().toString(), true, false, false, null).getQueue()); + } + closeAndWaitForRecovery(); + expectChannelRecovery(channel); + for(String q : qs) { + expectQueueRecovery(channel, q); + channel.queueDelete(q); + } + } + + @Test public void channelRecoveryCallback() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(2); + final CountDownLatch startLatch = new CountDownLatch(2); + final RecoveryListener listener = new RecoveryListener() { + @Override + public void handleRecovery(Recoverable recoverable) { + latch.countDown(); + } + @Override + public void handleRecoveryStarted(Recoverable recoverable) { + startLatch.countDown(); + } + }; + RecoverableChannel ch1 = (RecoverableChannel) connection.createChannel(); + ch1.addRecoveryListener(listener); + RecoverableChannel ch2 = (RecoverableChannel) connection.createChannel(); + ch2.addRecoveryListener(listener); + + assertThat(ch1.isOpen()).isTrue(); + assertThat(ch2.isOpen()).isTrue(); + closeAndWaitForRecovery(); + expectChannelRecovery(ch1); + expectChannelRecovery(ch2); + wait(latch); + wait(startLatch); + } + + @Test public void basicAckAfterChannelRecovery() throws IOException, InterruptedException, TimeoutException { + final AtomicInteger consumed = new AtomicInteger(0); + int n = 5; + final CountDownLatch latch = new CountDownLatch(n); + Consumer consumer = new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, + Envelope envelope, + AMQP.BasicProperties properties, + byte[] body) throws IOException { + try { + if (consumed.intValue() > 0 && consumed.intValue() % 4 == 0) { + CountDownLatch recoveryLatch = prepareForRecovery(connection); + Host.closeConnection((AutorecoveringConnection)connection); + ConnectionRecovery.wait(recoveryLatch); + } + channel.basicAck(envelope.getDeliveryTag(), false); + } catch (InterruptedException e) { + // ignore + } finally { + consumed.incrementAndGet(); + latch.countDown(); + } + } + }; + + String q = channel.queueDeclare().getQueue(); + channel.basicConsume(q, consumer); + RecoverableConnection publishingConnection = newRecoveringConnection(false); + Channel publishingChannel = publishingConnection.createChannel(); + for (int i = 0; i < n; i++) { + publishingChannel.basicPublish("", q, null, "msg".getBytes()); + } + wait(latch); + publishingConnection.abort(); + } + + @Test public void consumersAreRemovedFromConnectionWhenChannelIsClosed() throws Exception { + RecoverableConnection connection = newRecoveringConnection(true); + try { + Field consumersField = AutorecoveringConnection.class.getDeclaredField("consumers"); + consumersField.setAccessible(true); + Map connectionConsumers = (Map) consumersField.get(connection); + + Channel channel1 = connection.createChannel(); + Channel channel2 = connection.createChannel(); + + assertThat(connectionConsumers).isEmpty(); + + String queue = channel1.queueDeclare().getQueue(); + + channel1.basicConsume(queue, true, new HashMap<>(), new DefaultConsumer(channel1)); + assertThat(connectionConsumers).hasSize(1); + channel1.basicConsume(queue, true, new HashMap<>(), new DefaultConsumer(channel1)); + assertThat(connectionConsumers).hasSize(2); + + channel2.basicConsume(queue, true, new HashMap<>(), new DefaultConsumer(channel2)); + assertThat(connectionConsumers).hasSize(3); + + channel1.close(); + assertThat(connectionConsumers).hasSize(3 - 2); + + channel2.close(); + assertThat(connectionConsumers).isEmpty(); + } finally { + connection.abort(); + } + } + + @Test public void recoveryWithExponentialBackoffDelayHandler() throws Exception { + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + connectionFactory.setRecoveryDelayHandler(new RecoveryDelayHandler.ExponentialBackoffDelayHandler()); + Connection testConnection = connectionFactory.newConnection(UUID.randomUUID().toString()); + try { + assertThat(testConnection.isOpen()).isTrue(); + TestUtils.closeAndWaitForRecovery((RecoverableConnection) testConnection); + assertThat(testConnection.isOpen()).isTrue(); + } finally { + connection.close(); + } + } + + @Test public void recoveryWithMultipleThreads() throws Exception { + // test with 8 recovery threads + final ThreadPoolExecutor executor = new ThreadPoolExecutor(8, 8, 30, TimeUnit.SECONDS, + new LinkedBlockingQueue<>()); + executor.allowCoreThreadTimeOut(true); + ConnectionFactory connectionFactory = buildConnectionFactoryWithRecoveryEnabled(false); + assertThat(connectionFactory.getTopologyRecoveryExecutor()).isNull(); + connectionFactory.setTopologyRecoveryExecutor(executor); + assertThat(connectionFactory.getTopologyRecoveryExecutor()).isEqualTo(executor); + RecoverableConnection testConnection = (RecoverableConnection) connectionFactory.newConnection( + UUID.randomUUID().toString() + ); + try { + final List channels = new ArrayList(); + final List exchanges = new ArrayList(); + final List queues = new ArrayList(); + // create 16 channels + final int channelCount = 16; + final int queuesPerChannel = 20; + final CountDownLatch latch = new CountDownLatch(channelCount * queuesPerChannel); + for (int i=0; i < channelCount; i++) { + final Channel testChannel = testConnection.createChannel(); + channels.add(testChannel); + String x = "tmp-x-topic-" + i; + exchanges.add(x); + testChannel.exchangeDeclare(x, "topic"); + // create 20 queues and bindings per channel + for (int j=0; j < queuesPerChannel; j++) { + String q = "tmp-q-" + i + "-" + j; + queues.add(q); + testChannel.queueDeclare(q, false, false, true, null); + testChannel.queueBind(q, x, "tmp-key-" + i + "-" + j); + testChannel.basicConsume(q, new DefaultConsumer(testChannel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) + throws IOException { + testChannel.basicAck(envelope.getDeliveryTag(), false); + latch.countDown(); + } + }); + } + } + // now do recovery + TestUtils.closeAndWaitForRecovery(testConnection); + + // verify channels & topology recovered by publishing a message to each + for (int i=0; i < channelCount; i++) { + Channel ch = channels.get(i); + expectChannelRecovery(ch); + // publish message to each queue/consumer + for (int j=0; j < queuesPerChannel; j++) { + ch.basicPublish("tmp-x-topic-" + i, "tmp-key-" + i + "-" + j, null, "msg".getBytes()); + } + } + // verify all queues/consumers got it + assertThat(latch.await(30, TimeUnit.SECONDS)).isTrue(); + + // cleanup + Channel cleanupChannel = testConnection.createChannel(); + for (String q : queues) + cleanupChannel.queueDelete(q); + for (String x : exchanges) + cleanupChannel.exchangeDelete(x); + } finally { + testConnection.close(); + } + } + + @Test public void thatBindingFromDeletedExchangeIsDeleted() throws IOException, InterruptedException { + String q = generateQueueName(); + channel.queueDeclare(q, false, false, false, null); + try { + String x = generateExchangeName(); + channel.exchangeDeclare(x, "fanout"); + channel.queueBind(q, x, ""); + assertRecordedBinding(connection, 1); + channel.exchangeDelete(x); + assertRecordedBinding(connection, 0); + } finally { + channel.queueDelete(q); + } + } + + private void assertConsumerCount(int exp, String q) throws IOException { + assertThat(channel.queueDeclarePassive(q).getConsumerCount()).isEqualTo(exp); + } + + private static AMQP.Queue.DeclareOk declareClientNamedQueue(Channel ch, String q) throws IOException { + return ch.queueDeclare(q, true, false, false, null); + } + + private static AMQP.Queue.DeclareOk declareClientNamedAutoDeleteQueue(Channel ch, String q) throws IOException { + return ch.queueDeclare(q, true, false, true, null); + } + + + private static void declareClientNamedQueueNoWait(Channel ch, String q) throws IOException { + ch.queueDeclareNoWait(q, true, false, false, null); + } + + private static AMQP.Exchange.DeclareOk declareExchange(Channel ch, String x) throws IOException { + return ch.exchangeDeclare(x, "fanout", false); + } + + private static void declareExchangeNoWait(Channel ch, String x) throws IOException { + ch.exchangeDeclareNoWait(x, "fanout", false, false, false, null); + } + + private static void expectQueueRecovery(Channel ch, String q) throws IOException, InterruptedException, TimeoutException { + ch.confirmSelect(); + ch.queuePurge(q); + AMQP.Queue.DeclareOk ok1 = declareClientNamedQueue(ch, q); + assertThat(ok1.getMessageCount()).isEqualTo(0); + ch.basicPublish("", q, null, "msg".getBytes()); + waitForConfirms(ch); + AMQP.Queue.DeclareOk ok2 = declareClientNamedQueue(ch, q); + assertThat(ok2.getMessageCount()).isEqualTo(1); + } + + private static void expectAutoDeleteQueueAndBindingRecovery(Channel ch, String x, String q) throws IOException, InterruptedException, + TimeoutException { + ch.confirmSelect(); + ch.queuePurge(q); + AMQP.Queue.DeclareOk ok1 = declareClientNamedAutoDeleteQueue(ch, q); + assertThat(ok1.getMessageCount()).isEqualTo(0); + ch.exchangeDeclare(x, "fanout"); + ch.basicPublish(x, "", null, "msg".getBytes()); + waitForConfirms(ch); + AMQP.Queue.DeclareOk ok2 = declareClientNamedAutoDeleteQueue(ch, q); + assertThat(ok2.getMessageCount()).isEqualTo(1); + } + + private static void expectExchangeRecovery(Channel ch, String x) throws IOException, InterruptedException, TimeoutException { + ch.confirmSelect(); + String q = ch.queueDeclare().getQueue(); + final String rk = "routing-key"; + ch.queueBind(q, x, rk); + ch.basicPublish(x, rk, null, "msg".getBytes()); + waitForConfirms(ch); + ch.exchangeDeclarePassive(x); + } + + private static CountDownLatch prepareForShutdown(Connection conn) { + final CountDownLatch latch = new CountDownLatch(1); + conn.addShutdownListener(cause -> latch.countDown()); + return latch; + } + + private void closeAndWaitForRecovery() throws IOException, InterruptedException { + TestUtils.closeAndWaitForRecovery((AutorecoveringConnection)this.connection); + } + + private void restartPrimaryAndWaitForRecovery() throws IOException, InterruptedException { + restartPrimaryAndWaitForRecovery(this.connection); + } + + private void restartPrimaryAndWaitForRecovery(Connection connection) throws IOException, InterruptedException { + CountDownLatch latch = prepareForRecovery(connection); + // restart without tearing down and setting up + // new connection and channel + bareRestart(); + wait(latch); + } + + private static void expectChannelRecovery(Channel ch) { + assertThat(ch.isOpen()).isTrue(); + } + + @Override + protected ConnectionFactory newConnectionFactory() { + return buildConnectionFactoryWithRecoveryEnabled(false); + } + + private static RecoverableConnection newRecoveringConnection(boolean disableTopologyRecovery) + throws IOException, TimeoutException { + ConnectionFactory cf = buildConnectionFactoryWithRecoveryEnabled(disableTopologyRecovery); + return (AutorecoveringConnection) cf.newConnection(UUID.randomUUID().toString()); + } + + private static RecoverableConnection newRecoveringConnection(Address[] addresses) + throws IOException, TimeoutException { + ConnectionFactory cf = buildConnectionFactoryWithRecoveryEnabled(false); + // specifically use the Address[] overload + return (AutorecoveringConnection) cf.newConnection(addresses, UUID.randomUUID().toString()); + } + + private static RecoverableConnection newRecoveringConnection(boolean disableTopologyRecovery, List
addresses) + throws IOException, TimeoutException { + ConnectionFactory cf = buildConnectionFactoryWithRecoveryEnabled(disableTopologyRecovery); + return (AutorecoveringConnection) cf.newConnection(addresses, UUID.randomUUID().toString()); + } + + private static RecoverableConnection newRecoveringConnection(List
addresses) + throws IOException, TimeoutException { + return newRecoveringConnection(false, addresses); + } + + private static RecoverableConnection newRecoveringConnection(boolean disableTopologyRecovery, String connectionName) + throws IOException, TimeoutException { + ConnectionFactory cf = buildConnectionFactoryWithRecoveryEnabled(disableTopologyRecovery); + return (RecoverableConnection) cf.newConnection(connectionName); + } + + private static RecoverableConnection newRecoveringConnection(String connectionName) + throws IOException, TimeoutException { + return newRecoveringConnection(false, connectionName); + } + + private static ConnectionFactory buildConnectionFactoryWithRecoveryEnabled(boolean disableTopologyRecovery) { + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.setNetworkRecoveryInterval(RECOVERY_INTERVAL); + cf.setAutomaticRecoveryEnabled(true); + if (disableTopologyRecovery) { + cf.setTopologyRecoveryEnabled(false); + } + return cf; + } + + private static void wait(CountDownLatch latch) throws InterruptedException { + // we want to wait for recovery to complete for a reasonable amount of time + // but still make recovery failures easy to notice in development environments + assertThat(latch.await(90, TimeUnit.SECONDS)).isTrue(); + } + + private static void waitForConfirms(Channel ch) throws InterruptedException, TimeoutException { + ch.waitForConfirms(30 * 60 * 1000); + } + + private static void assertRecordedQueues(Connection conn, int size) { + assertThat(((AutorecoveringConnection)conn).getRecordedQueues()).hasSize(size); + } + + private static void assertRecordedExchanges(Connection conn, int size) { + assertThat(((AutorecoveringConnection)conn).getRecordedExchanges()).hasSize(size); + } + + private static void assertRecordedBinding(Connection conn, int size) { + assertThat(((AutorecoveringConnection)conn).getRecordedBindings()).hasSize(size); + } +} diff --git a/test/src/com/rabbitmq/client/test/functional/ConsumerCancelNotification.java b/src/test/java/com/rabbitmq/client/test/functional/ConsumerCancelNotification.java similarity index 54% rename from test/src/com/rabbitmq/client/test/functional/ConsumerCancelNotification.java rename to src/test/java/com/rabbitmq/client/test/functional/ConsumerCancelNotification.java index 7a43ba94d2..1bd12f17c2 100644 --- a/test/src/com/rabbitmq/client/test/functional/ConsumerCancelNotification.java +++ b/src/test/java/com/rabbitmq/client/test/functional/ConsumerCancelNotification.java @@ -1,28 +1,26 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Consumer; import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.ConsumerCancelledException; -import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.ShutdownSignalException; import com.rabbitmq.client.test.BrokerTestCase; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.concurrent.ArrayBlockingQueue; @@ -30,16 +28,19 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + public class ConsumerCancelNotification extends BrokerTestCase { private final String queue = "cancel_notification_queue"; - public void testConsumerCancellationNotification() throws IOException, + @Test public void consumerCancellationNotification() throws IOException, InterruptedException { final BlockingQueue result = new ArrayBlockingQueue(1); channel.queueDeclare(queue, false, true, false, null); - Consumer consumer = new QueueingConsumer(channel) { + Consumer consumer = new DefaultConsumer(channel) { @Override public void handleCancel(String consumerTag) throws IOException { try { @@ -54,38 +55,6 @@ public void handleCancel(String consumerTag) throws IOException { assertTrue(result.take()); } - public void testConsumerCancellationInterruptsQueuingConsumerWait() - throws IOException, InterruptedException { - final BlockingQueue result = new ArrayBlockingQueue(1); - channel.queueDeclare(queue, false, true, false, null); - final QueueingConsumer consumer = new QueueingConsumer(channel); - Runnable receiver = new Runnable() { - - public void run() { - try { - try { - consumer.nextDelivery(); - } catch (ConsumerCancelledException e) { - result.put(true); - return; - } catch (ShutdownSignalException e) { - } catch (InterruptedException e) { - } - result.put(false); - } catch (InterruptedException e) { - fail(); - } - } - }; - Thread t = new Thread(receiver); - t.start(); - channel.basicConsume(queue, consumer); - channel.queueDelete(queue); - assertTrue(result.take()); - t.join(); - } - - class AlteringConsumer extends DefaultConsumer { private final String altQueue; private final CountDownLatch latch; @@ -113,7 +82,7 @@ public void handleCancel(String consumerTag) { } } - public void testConsumerCancellationHandlerUsesBlockingOperations() + @Test public void consumerCancellationHandlerUsesBlockingOperations() throws IOException, InterruptedException { final String altQueue = "basic.cancel.fallback"; channel.queueDeclare(queue, false, true, false, null); diff --git a/src/test/java/com/rabbitmq/client/test/functional/ConsumerCount.java b/src/test/java/com/rabbitmq/client/test/functional/ConsumerCount.java new file mode 100644 index 0000000000..68f43fa7c8 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/ConsumerCount.java @@ -0,0 +1,39 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.test.BrokerTestCase; + +public class ConsumerCount extends BrokerTestCase { + @Test public void consumerCount() throws IOException { + String q = generateQueueName(); + channel.queueDeclare(q, false, true, false, null); + assertEquals(0, channel.consumerCount(q)); + + String tag = channel.basicConsume(q, new DefaultConsumer(channel)); + assertEquals(1, channel.consumerCount(q)); + + channel.basicCancel(tag); + assertEquals(0, channel.consumerCount(q)); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/ConsumerPriorities.java b/src/test/java/com/rabbitmq/client/test/functional/ConsumerPriorities.java new file mode 100644 index 0000000000..47703f3414 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/ConsumerPriorities.java @@ -0,0 +1,137 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import com.rabbitmq.client.test.BrokerTestCase; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import java.util.concurrent.CountDownLatch; + +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.MessageProperties; + +public class ConsumerPriorities extends BrokerTestCase { + + @Test public void validation() throws IOException { + assertFailValidation(args("banana")); + assertFailValidation(args(new HashMap())); + assertFailValidation(args(null)); + assertFailValidation(args(Arrays.asList(1, 2, 3))); + } + + private void assertFailValidation(Map args) throws IOException { + Channel ch = connection.createChannel(); + String queue = ch.queueDeclare().getQueue(); + try { + ch.basicConsume(queue, true, args, new DefaultConsumer(ch)); + fail("Validation should fail for " + args); + } catch (IOException ioe) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, ioe); + } + } + + private static final int COUNT = 10; + private static final long DELIVERY_TIMEOUT_MS = 100; + private static final long CANCEL_OK_TIMEOUT_MS = 10 * 1000; + + @Test public void consumerPriorities() throws Exception { + String queue = channel.queueDeclare().getQueue(); + QueueMessageConsumer highConsumer = new QueueMessageConsumer(channel); + QueueMessageConsumer medConsumer = new QueueMessageConsumer(channel); + QueueMessageConsumer lowConsumer = new QueueMessageConsumer(channel); + String high = channel.basicConsume(queue, true, args(1), highConsumer); + String med = channel.basicConsume(queue, true, medConsumer); + channel.basicConsume(queue, true, args(-1), lowConsumer); + + publish(queue, COUNT, "high"); + assertContents(highConsumer, COUNT, "high"); + channel.basicCancel(high); + assertTrue( + highConsumer.cancelLatch.await(CANCEL_OK_TIMEOUT_MS, TimeUnit.MILLISECONDS), + "High priority consumer should have been cancelled" + ); + publish(queue, COUNT, "med"); + assertContents(medConsumer, COUNT, "med"); + channel.basicCancel(med); + assertTrue( + medConsumer.cancelLatch.await(CANCEL_OK_TIMEOUT_MS, TimeUnit.MILLISECONDS), + "Medium priority consumer should have been cancelled" + ); + publish(queue, COUNT, "low"); + assertContents(lowConsumer, COUNT, "low"); + } + + private Map args(Object o) { + Map map = new HashMap(); + map.put("x-priority", o); + return map; + } + + private void assertContents(QueueMessageConsumer qc, int count, String msg) throws InterruptedException { + for (int i = 0; i < count; i++) { + byte[] body = qc.nextDelivery(DELIVERY_TIMEOUT_MS); + assertEquals(msg, new String(body)); + } + assertEquals(null, qc.nextDelivery(DELIVERY_TIMEOUT_MS)); + } + + private void publish(String queue, int count, String msg) throws IOException { + for (int i = 0; i < count; i++) { + channel.basicPublish("", queue, MessageProperties.MINIMAL_BASIC, msg.getBytes()); + } + } + + private static class QueueMessageConsumer extends DefaultConsumer { + + BlockingQueue messages = new LinkedBlockingQueue(); + + CountDownLatch cancelLatch = new CountDownLatch(1); + + public QueueMessageConsumer(Channel channel) { + super(channel); + } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + messages.add(body); + } + + @Override + public void handleCancelOk(String consumerTag) { + cancelLatch.countDown(); + } + + byte[] nextDelivery(long timeoutInMs) throws InterruptedException { + return messages.poll(timeoutInMs, TimeUnit.MILLISECONDS); + } + + } +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/DeadLetterExchange.java b/src/test/java/com/rabbitmq/client/test/functional/DeadLetterExchange.java new file mode 100644 index 0000000000..9acef4850a --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/DeadLetterExchange.java @@ -0,0 +1,742 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.AMQP.BasicProperties; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.client.test.TestUtils; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.*; + +import static com.rabbitmq.client.test.TestUtils.safeDelete; +import static com.rabbitmq.client.test.TestUtils.waitAtMost; +import static java.time.Duration.ofSeconds; +import static org.junit.jupiter.api.Assertions.*; + +public class DeadLetterExchange extends BrokerTestCase { + + public static final String DLX = "dead.letter.exchange"; + private static final String DLX_ARG = "x-dead-letter-exchange"; + private static final String DLX_RK_ARG = "x-dead-letter-routing-key"; + public static final String TEST_QUEUE_NAME = "test.queue.dead.letter"; + public static final String DLQ = "queue.dlq"; + private static final String DLQ2 = "queue.dlq2"; + public static final int MSG_COUNT = 10; + private static final int TTL = 2000; + + @Override + protected void createResources() throws IOException { + channel.exchangeDelete(DLX); + channel.queueDelete(DLQ); + channel.exchangeDeclare(DLX, "direct"); + channel.queueDeclare(DLQ, false, true, false, null); + } + + @Override + protected void releaseResources() throws IOException { + channel.exchangeDelete(DLX); + channel.queueDelete(TEST_QUEUE_NAME); + } + + @Test public void declareQueueWithExistingDeadLetterExchange() + throws IOException + { + declareQueue(DLX); + } + + @Test public void declareQueueWithNonExistingDeadLetterExchange() + throws IOException + { + declareQueue("some.random.exchange.name"); + } + + @Test public void declareQueueWithEquivalentDeadLetterExchange() + throws IOException + { + declareQueue(DLX); + declareQueue(DLX); + } + + @Test public void declareQueueWithEquivalentDeadLetterRoutingKey() + throws IOException + { + declareQueue(TEST_QUEUE_NAME, DLX, "routing_key", null); + declareQueue(TEST_QUEUE_NAME, DLX, "routing_key", null); + } + + @Test public void declareQueueWithInvalidDeadLetterExchangeArg() { + try { + declareQueue(133); + fail("x-dead-letter-exchange must be a valid exchange name"); + } catch (IOException ex) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); + } + } + + @Test public void redeclareQueueWithInvalidDeadLetterExchangeArg() + throws IOException + { + declareQueue("inequivalent_dlx_name", "dlx_foo", null, null); + try { + declareQueue("inequivalent_dlx_name", "dlx_bar", null, null); + fail("x-dead-letter-exchange must be a valid exchange name " + + "and must not change in subsequent declarations"); + } catch (IOException ex) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); + } + } + + @Test public void declareQueueWithInvalidDeadLetterRoutingKeyArg() { + try { + declareQueue("foo", "amq.direct", 144, null); + fail("x-dead-letter-routing-key must be a string"); + } catch (IOException ex) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); + } + } + + @Test public void redeclareQueueWithInvalidDeadLetterRoutingKeyArg() + throws IOException + { + declareQueue("inequivalent_dlx_rk", "amq.direct", "dlx_rk", null); + try { + declareQueue("inequivalent_dlx_rk", "amq.direct", "dlx_rk2", null); + fail("x-dead-letter-routing-key must be a string and must not " + + "change in subsequent declarations"); + } catch (IOException ex) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); + } + } + + @Test public void declareQueueWithRoutingKeyButNoDeadLetterExchange() { + try { + Map args = new HashMap<>(); + args.put(DLX_RK_ARG, "foo"); + + channel.queueDeclare(randomQueueName(), false, true, false, args); + fail("dlx must be defined if dl-rk is set"); + } catch (IOException ex) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); + } + } + + @Test public void redeclareQueueWithRoutingKeyButNoDeadLetterExchange() { + try { + String queueName = randomQueueName(); + Map args = new HashMap<>(); + channel.queueDeclare(queueName, false, true, false, args); + + args.put(DLX_RK_ARG, "foo"); + + channel.queueDeclare(queueName, false, true, false, args); + fail("x-dead-letter-exchange must be specified if " + + "x-dead-letter-routing-key is set"); + } catch (IOException ex) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); + } + } + + @Test public void deadLetterQueueTTLExpiredMessages() throws Exception { + ttlTest(TTL); + } + + @Test public void deadLetterQueueZeroTTLExpiredMessages() throws Exception { + ttlTest(0); + } + + @Test public void deadLetterQueueTTLPromptExpiry() throws Exception { + Map args = new HashMap<>(); + args.put("x-message-ttl", TTL); + declareQueue(TEST_QUEUE_NAME, DLX, null, args); + channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); + channel.queueBind(DLQ, DLX, "test"); + + //measure round-trip latency + AccumulatingMessageConsumer c = new AccumulatingMessageConsumer(channel); + String cTag = channel.basicConsume(TEST_QUEUE_NAME, true, c); + long start = System.currentTimeMillis(); + publish(null, "test"); + byte[] body = c.nextDelivery(TTL); + long stop = System.currentTimeMillis(); + assertNotNull(body); + channel.basicCancel(cTag); + long latency = stop-start; + + channel.basicConsume(DLQ, true, c); + + // publish messages at regular intervals until currentTime + + // 3/4th of TTL + int count = 0; + start = System.currentTimeMillis(); + stop = start + TTL * 3 / 4; + long now = start; + while (now < stop) { + publish(null, Long.toString(now)); + count++; + Thread.sleep(TTL / 100); + now = System.currentTimeMillis(); + } + + checkPromptArrival(c, count, latency); + + start = System.currentTimeMillis(); + // publish message - which kicks off the queue's ttl timer - + // and immediately fetch it in noack mode + publishAt(start); + basicGet(TEST_QUEUE_NAME); + // publish a 2nd message and immediately fetch it in ack mode + publishAt(start + TTL / 2); + GetResponse r = channel.basicGet(TEST_QUEUE_NAME, false); + // publish a 3rd message + publishAt(start + TTL * 3 / 4); + // reject 2nd message after the initial timer has fired but + // before the message is due to expire + waitUntil(start + TTL * 5 / 4); + channel.basicReject(r.getEnvelope().getDeliveryTag(), true); + + checkPromptArrival(c, 2, latency); + } + + @Test public void deadLetterDeletedDLX() throws Exception { + declareQueue(TEST_QUEUE_NAME, DLX, null, null, 1); + channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); + channel.queueBind(DLQ, DLX, "test"); + + channel.exchangeDelete(DLX); + publishN(MSG_COUNT); + sleep(100); + + consumeN(DLQ, 0, WithResponse.NULL); + + channel.exchangeDeclare(DLX, "direct"); + channel.queueBind(DLQ, DLX, "test"); + + publishN(MSG_COUNT); + sleep(100); + + consumeN(DLQ, MSG_COUNT, WithResponse.NULL); + } + + @SuppressWarnings("unchecked") + @Test public void deadLetterPerMessageTTLRemoved() throws Exception { + declareQueue(TEST_QUEUE_NAME, DLX, null, null, 1); + channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); + channel.queueBind(DLQ, DLX, "test"); + + final BasicProperties props = MessageProperties.BASIC.builder().expiration("100").build(); + publish(props, "test message"); + + // The message's expiration property should have been removed, thus + // after 100ms of hitting the queue, the message should get routed to + // the DLQ *AND* should remain there, not getting removed after a subsequent + // wait time > 100ms + sleep(500); + consumeN(DLQ, 1, getResponse -> { + assertNull(getResponse.getProps().getExpiration()); + Map headers = getResponse.getProps().getHeaders(); + assertNotNull(headers); + ArrayList death = (ArrayList) headers.get("x-death"); + assertNotNull(death); + assertDeathReason(death, 0, TEST_QUEUE_NAME, "expired"); + final Map deathHeader = (Map) death.get(0); + assertEquals("100", deathHeader.get("original-expiration").toString()); + }); + } + + @Test public void deadLetterOnReject() throws Exception { + rejectionTest(false); + } + + @Test public void deadLetterOnNack() throws Exception { + rejectionTest(true); + } + + @Test public void deadLetterNoDeadLetterQueue() throws IOException { + channel.queueDelete(DLQ); + + declareQueue(TEST_QUEUE_NAME, DLX, null, null, 1); + channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); + + publishN(MSG_COUNT); + } + + @Test public void deadLetterMultipleDeadLetterQueues() + throws IOException + { + declareQueue(TEST_QUEUE_NAME, DLX, null, null, 1); + + channel.queueDeclare(DLQ2, false, true, false, null); + + channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); + channel.queueBind(DLQ, DLX, "test"); + channel.queueBind(DLQ2, DLX, "test"); + + publishN(MSG_COUNT); + } + + @SuppressWarnings("unchecked") + @Test public void deadLetterTwice() throws Exception { + declareQueue(TEST_QUEUE_NAME, DLX, null, null, 1); + + channel.queueDelete(DLQ); + declareQueue(DLQ, DLX, null, null, 1); + + channel.queueDeclare(DLQ2, false, true, false, null); + + channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); + channel.queueBind(DLQ, DLX, "test"); + channel.queueBind(DLQ2, DLX, "test"); + + publishN(MSG_COUNT); + + sleep(100); + + // There should now be two copies of each message on DLQ2: one + // with one set of death headers, and another with two sets. + consumeN(DLQ2, MSG_COUNT*2, getResponse -> { + Map headers = getResponse.getProps().getHeaders(); + assertNotNull(headers); + ArrayList death = (ArrayList) headers.get("x-death"); + assertNotNull(death); + if (death.size() == 1) { + assertDeathReason(death, 0, TEST_QUEUE_NAME, "expired"); + } else if (death.size() == 2) { + assertDeathReason(death, 0, DLQ, "expired"); + assertDeathReason(death, 1, TEST_QUEUE_NAME, "expired"); + } else { + fail("message was dead-lettered more times than expected"); + } + }); + } + + @Test public void deadLetterSelf() throws Exception { + declareQueue(TEST_QUEUE_NAME, "amq.direct", "test", null, 1); + channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); + + publishN(MSG_COUNT); + // This test hangs if the queue doesn't process ALL the + // messages before being deleted, so make sure the next + // sleep is long enough. + sleep(200); + + // The messages will NOT be dead-lettered to self. + consumeN(TEST_QUEUE_NAME, 0, WithResponse.NULL); + } + + @Test public void deadLetterCycle() throws Exception { + // testDeadLetterTwice and testDeadLetterSelf both test that we drop + // messages in pure-expiry cycles. So we just need to test that + // non-pure-expiry cycles do not drop messages. + + String queue1 = generateQueueName(); + String queue2 = generateQueueName(); + try { + declareQueue(queue1, "", queue2, null, 1); + declareQueue(queue2, "", queue1, null, 0); + + channel.basicPublish("", queue1, MessageProperties.BASIC, "".getBytes()); + final CountDownLatch latch = new CountDownLatch(10); + channel.basicConsume(queue2, false, + new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, + AMQP.BasicProperties properties, byte[] body) throws IOException { + channel.basicReject(envelope.getDeliveryTag(), false); + latch.countDown(); + } + }); + assertTrue(latch.await(10, TimeUnit.SECONDS)); + } finally { + safeDelete(connection, queue1); + safeDelete(connection, queue2); + } + } + + @SuppressWarnings("unchecked") + @Test public void deadLetterNewRK() throws Exception { + declareQueue(TEST_QUEUE_NAME, DLX, "test-other", null, 1); + + channel.queueDeclare(DLQ2, false, true, false, null); + + channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); + channel.queueBind(DLQ, DLX, "test"); + channel.queueBind(DLQ2, DLX, "test-other"); + + Map headers = new HashMap<>(); + headers.put("CC", Collections.singletonList("foo")); + headers.put("BCC", Collections.singletonList("bar")); + + publishN(MSG_COUNT, (new AMQP.BasicProperties.Builder()) + .headers(headers) + .build()); + + sleep(100); + + consumeN(DLQ, 0, WithResponse.NULL); + consumeN(DLQ2, MSG_COUNT, getResponse -> { + Map headers1 = getResponse.getProps().getHeaders(); + assertNotNull(headers1); + if (beforeMessageContainers()) { + assertNull(headers1.get("CC")); + } + assertNull(headers1.get("BCC")); + + ArrayList death = (ArrayList) headers1.get("x-death"); + assertNotNull(death); + assertEquals(1, death.size()); + assertDeathReason(death, 0, TEST_QUEUE_NAME, + "expired", "amq.direct", + Arrays.asList("test", "foo")); + }); + } + + @SuppressWarnings("unchecked") + @Test public void republish() throws Exception { + if (beforeMessageContainers()) { + Map args = new HashMap<>(); + args.put("x-message-ttl", 100); + declareQueue(TEST_QUEUE_NAME, DLX, null, args); + channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); + channel.queueBind(DLQ, DLX, "test"); + publishN(1); + + AtomicReference responseRefeference = new AtomicReference<>(); + waitAtMost( + ofSeconds(1), + () -> { + GetResponse response = channel.basicGet(DLQ, true); + responseRefeference.set(response); + return responseRefeference.get() != null; + }); + GetResponse getResponse = responseRefeference.get(); + assertNotNull(getResponse, "Message not dead-lettered"); + assertEquals("test message", new String(getResponse.getBody())); + BasicProperties props = getResponse.getProps(); + Map headers = props.getHeaders(); + assertNotNull(headers); + ArrayList death = (ArrayList) headers.get("x-death"); + assertNotNull(death); + assertEquals(1, death.size()); + assertDeathReason(death, 0, TEST_QUEUE_NAME, "expired", "amq.direct", + Collections.singletonList("test")); + + // Make queue zero length + args = new HashMap<>(); + args.put("x-max-length", 0); + channel.queueDelete(TEST_QUEUE_NAME); + declareQueue(TEST_QUEUE_NAME, DLX, null, args); + channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); + + sleep(100); + //Queueing second time with same props + channel.basicPublish("amq.direct", "test", + new AMQP.BasicProperties.Builder() + .headers(headers) + .build(), "test message".getBytes()); + + responseRefeference.set(null); + waitAtMost( + ofSeconds(1), + () -> { + GetResponse response = channel.basicGet(DLQ, true); + responseRefeference.set(response); + return responseRefeference.get() != null; + }); + getResponse = responseRefeference.get(); + assertNotNull(getResponse, "Message not dead-lettered"); + assertEquals("test message", new String(getResponse.getBody())); + headers = getResponse.getProps().getHeaders(); + assertNotNull(headers); + death = (ArrayList) headers.get("x-death"); + assertNotNull(death); + assertEquals(2, death.size()); + assertDeathReason(death, 0, TEST_QUEUE_NAME, "maxlen", "amq.direct", + Collections.singletonList("test")); + assertDeathReason(death, 1, TEST_QUEUE_NAME, "expired", "amq.direct", + Collections.singletonList("test")); + + //Set invalid headers + headers.put("x-death", "[I, am, not, array]"); + channel.basicPublish("amq.direct", "test", + new AMQP.BasicProperties.Builder() + .headers(headers) + .build(), "test message".getBytes()); + + responseRefeference.set(null); + waitAtMost( + ofSeconds(1), + () -> { + GetResponse response = channel.basicGet(DLQ, true); + responseRefeference.set(response); + return responseRefeference.get() != null; + }); + getResponse = responseRefeference.get(); + + assertNotNull(getResponse, "Message not dead-lettered"); + assertEquals("test message", new String(getResponse.getBody())); + headers = getResponse.getProps().getHeaders(); + assertNotNull(headers); + death = (ArrayList) headers.get("x-death"); + assertNotNull(death); + assertEquals(1, death.size()); + assertDeathReason(death, 0, TEST_QUEUE_NAME, "maxlen", "amq.direct", + Collections.singletonList("test")); + + } + } + + private void rejectionTest(final boolean useNack) throws Exception { + deadLetterTest((Callable) () -> { + for (int x = 0; x < MSG_COUNT; x++) { + GetResponse getResponse = + channel.basicGet(TEST_QUEUE_NAME, false); + long tag = getResponse.getEnvelope().getDeliveryTag(); + if (useNack) { + channel.basicNack(tag, false, false); + } else { + channel.basicReject(tag, false); + } + } + return null; + }, null, "rejected"); + } + + private void deadLetterTest(final Runnable deathTrigger, + Map queueDeclareArgs, + String reason) + throws Exception + { + deadLetterTest(() -> { + deathTrigger.run(); + return null; + }, queueDeclareArgs, reason); + } + + private void deadLetterTest(Callable deathTrigger, + Map queueDeclareArgs, + final String reason) + throws Exception + { + declareQueue(TEST_QUEUE_NAME, DLX, null, queueDeclareArgs); + + channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); + channel.queueBind(DLQ, DLX, "test"); + + publishN(MSG_COUNT); + + deathTrigger.call(); + + consume(channel, reason); + } + + @SuppressWarnings("unchecked") + public static void consume(final Channel channel, final String reason) throws IOException { + consumeN(channel, DLQ, MSG_COUNT, getResponse -> { + Map headers = getResponse.getProps().getHeaders(); + assertNotNull(headers); + ArrayList death = (ArrayList) headers.get("x-death"); + assertNotNull(death); + // the following assertions shouldn't be checked on version lower than 3.7 + // as the headers are new in 3.7 + // see https://github.com/rabbitmq/rabbitmq-server/issues/1332 + if(TestUtils.isVersion37orLater(channel.getConnection())) { + assertNotNull(headers.get("x-first-death-queue")); + assertNotNull(headers.get("x-first-death-reason")); + assertNotNull(headers.get("x-first-death-exchange")); + } + assertEquals(1, death.size()); + assertDeathReason(death, 0, TEST_QUEUE_NAME, reason, + "amq.direct", + Collections.singletonList("test")); + }); + } + + private void ttlTest(final long ttl) throws Exception { + Map args = new HashMap(); + args.put("x-message-ttl", ttl); + deadLetterTest(() -> sleep(ttl + 1500), args, "expired"); + } + + private void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException ex) { + // whoosh + } + } + + /* check that each message arrives within epsilon of the + publication time + TTL + latency */ + private void checkPromptArrival(AccumulatingMessageConsumer c, + int count, long latency) throws Exception { + long epsilon = TTL / 5; + for (int i = 0; i < count; i++) { + byte[] body = c.nextDelivery(TTL + TTL + latency + epsilon); + assertNotNull(body, "message #" + i + " did not expire"); + long now = System.currentTimeMillis(); + long publishTime = Long.valueOf(new String(body)); + long targetTime = publishTime + TTL + latency; + assertTrue((now >= targetTime - epsilon) && + (now <= targetTime + epsilon), + "expiry outside bounds (+/- " + epsilon + "): " + + (now - targetTime)); + } + } + + private void declareQueue(Object deadLetterExchange) throws IOException { + declareQueue(TEST_QUEUE_NAME, deadLetterExchange, null, null); + } + + private void declareQueue(String queue, Object deadLetterExchange, + Object deadLetterRoutingKey, + Map args) throws IOException { + declareQueue(queue, deadLetterExchange, deadLetterRoutingKey, args, 0); + } + + private void declareQueue(String queue, Object deadLetterExchange, + Object deadLetterRoutingKey, + Map args, int ttl) + throws IOException + { + if (args == null) { + args = new HashMap<>(); + } + + if (ttl > 0){ + args.put("x-message-ttl", ttl); + } + + args.put(DLX_ARG, deadLetterExchange); + if (deadLetterRoutingKey != null) { + args.put(DLX_RK_ARG, deadLetterRoutingKey); + } + channel.queueDeclare(queue, false, false, false, args); + } + + private void publishN(int n) throws IOException { + publishN(n, null); + } + + private void publishN(int n, AMQP.BasicProperties props) + throws IOException + { + for(int x = 0; x < n; x++) { publish(props, "test message"); } + } + + private void publish(AMQP.BasicProperties props, String body) + throws IOException + { + channel.basicPublish("amq.direct", "test", props, body.getBytes()); + } + + private void publishAt(long when) throws Exception { + waitUntil(when); + publish(null, Long.toString(System.currentTimeMillis())); + } + + private void waitUntil(long when) throws Exception { + long delay = when - System.currentTimeMillis(); + Thread.sleep(delay > 0 ? delay : 0); + } + + private void consumeN(String queue, int n, WithResponse withResponse) + throws IOException + { + consumeN(channel, queue, n, withResponse); + } + + private static void consumeN(Channel channel, String queue, int n, WithResponse withResponse) + throws IOException + { + for(int x = 0; x < n; x++) { + GetResponse getResponse = + channel.basicGet(queue, true); + assertNotNull(getResponse, "Messages not dead-lettered (" + (n-x) + " left)"); + assertEquals("test message", new String(getResponse.getBody())); + withResponse.process(getResponse); + } + GetResponse getResponse = channel.basicGet(queue, true); + assertNull(getResponse, "expected empty queue"); + } + + @SuppressWarnings("unchecked") + private static void assertDeathReason(List death, int num, + String queue, String reason, + String exchange, List routingKeys) + { + Map deathHeader = + (Map)death.get(num); + assertEquals(exchange, deathHeader.get("exchange").toString()); + + List deathRKs = new ArrayList<>(); + for (Object rk : (ArrayList)deathHeader.get("routing-keys")) { + deathRKs.add(rk.toString()); + } + Collections.sort(deathRKs); + Collections.sort(routingKeys); + assertEquals(routingKeys, deathRKs); + + assertDeathReason(death, num, queue, reason); + } + + @SuppressWarnings("unchecked") + private static void assertDeathReason(List death, int num, + String queue, String reason) { + Map deathHeader = + (Map)death.get(num); + assertEquals(queue, deathHeader.get("queue").toString()); + assertEquals(reason, deathHeader.get("reason").toString()); + } + + private interface WithResponse { + WithResponse NULL = getResponse -> { + }; + + void process(GetResponse response); + } + + private static String randomQueueName() { + return DeadLetterExchange.class.getSimpleName() + "-" + UUID.randomUUID().toString(); + } + + class AccumulatingMessageConsumer extends DefaultConsumer { + + BlockingQueue messages = new LinkedBlockingQueue<>(); + + AccumulatingMessageConsumer(Channel channel) { + super(channel); + } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + messages.add(body); + } + + byte[] nextDelivery() { + return messages.poll(); + } + + byte[] nextDelivery(long timeoutInMs) throws InterruptedException { + return messages.poll(timeoutInMs, TimeUnit.MILLISECONDS); + } + + } +} diff --git a/test/src/com/rabbitmq/client/test/functional/DefaultExchange.java b/src/test/java/com/rabbitmq/client/test/functional/DefaultExchange.java similarity index 53% rename from test/src/com/rabbitmq/client/test/functional/DefaultExchange.java rename to src/test/java/com/rabbitmq/client/test/functional/DefaultExchange.java index bb94450b62..2613e6dc91 100644 --- a/test/src/com/rabbitmq/client/test/functional/DefaultExchange.java +++ b/src/test/java/com/rabbitmq/client/test/functional/DefaultExchange.java @@ -1,26 +1,29 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.test.BrokerTestCase; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.test.BrokerTestCase; + public class DefaultExchange extends BrokerTestCase { String queueName; @@ -32,12 +35,12 @@ protected void createResources() throws IOException { // See bug 22101: publish and declare are the only operations // permitted on the default exchange - public void testDefaultExchangePublish() throws IOException { + @Test public void defaultExchangePublish() throws IOException { basicPublishVolatile("", queueName); // Implicit binding assertDelivered(queueName, 1); } - public void testBindToDefaultExchange() throws IOException { + @Test public void bindToDefaultExchange() throws IOException { try { channel.queueBind(queueName, "", "foobar"); fail(); @@ -46,7 +49,7 @@ public void testBindToDefaultExchange() throws IOException { } } - public void testUnbindFromDefaultExchange() throws IOException { + @Test public void unbindFromDefaultExchange() throws IOException { try { channel.queueUnbind(queueName, "", queueName); fail(); @@ -55,7 +58,7 @@ public void testUnbindFromDefaultExchange() throws IOException { } } - public void testDeclareDefaultExchange() throws IOException { + @Test public void declareDefaultExchange() throws IOException { try { channel.exchangeDeclare("", "direct", true); fail(); @@ -64,7 +67,7 @@ public void testDeclareDefaultExchange() throws IOException { } } - public void testDeleteDefaultExchange() throws IOException { + @Test public void deleteDefaultExchange() throws IOException { try { channel.exchangeDelete(""); fail(); diff --git a/src/test/java/com/rabbitmq/client/test/functional/DirectReplyTo.java b/src/test/java/com/rabbitmq/client/test/functional/DirectReplyTo.java new file mode 100644 index 0000000000..7437c8f329 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/DirectReplyTo.java @@ -0,0 +1,140 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import com.rabbitmq.client.*; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.test.BrokerTestCase; + +public class DirectReplyTo extends BrokerTestCase { + private static final String QUEUE = "amq.rabbitmq.reply-to"; + + @Test public void roundTrip() throws IOException, InterruptedException { + QueueMessageConsumer c = new QueueMessageConsumer(channel); + String replyTo = rpcFirstHalf(c); + declare(connection, replyTo, true); + channel.confirmSelect(); + basicPublishVolatile("response".getBytes(), "", replyTo, MessageProperties.BASIC); + channel.waitForConfirms(); + + byte[] body = c.nextDelivery(10000); + assertEquals("response", new String(body)); + } + + @Test public void hack() throws IOException, InterruptedException { + QueueMessageConsumer c = new QueueMessageConsumer(channel); + String replyTo = rpcFirstHalf(c); + // 5 chars should overwrite part of the key but not the pid; aiming to prove + // we can't publish using just the pid + replyTo = replyTo.substring(0, replyTo.length() - 5) + "xxxxx"; + declare(connection, replyTo, false); + basicPublishVolatile("response".getBytes(), "", replyTo, MessageProperties.BASIC); + + byte[] body = c.nextDelivery(500); + assertNull(body); + } + + private void declare(Connection connection, String q, boolean expectedExists) throws IOException { + Channel ch = connection.createChannel(); + try { + ch.queueDeclarePassive(q); + assertTrue(expectedExists); + } catch (IOException e) { + assertFalse(expectedExists); + checkShutdownSignal(AMQP.NOT_FOUND, e); + // Hmmm... + channel = connection.createChannel(); + } + } + + @Test public void consumeFail() throws IOException, InterruptedException { + DefaultConsumer c = new DefaultConsumer(channel); + Channel ch = connection.createChannel(); + try { + ch.basicConsume(QUEUE, false, c); + } catch (IOException e) { + // Can't have ack mode + checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); + } + + ch = connection.createChannel(); + ch.basicConsume(QUEUE, true, c); + try { + ch.basicConsume(QUEUE, true, c); + } catch (IOException e) { + // Can't have multiple consumers + checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); + } + } + + @Test public void consumeSuccess() throws IOException { + DefaultConsumer c = new DefaultConsumer(channel); + String ctag = channel.basicConsume(QUEUE, true, c); + channel.basicCancel(ctag); + + String ctag2 = channel.basicConsume(QUEUE, true, c); + channel.basicCancel(ctag2); + assertNotSame(ctag, ctag2); + } + + private String rpcFirstHalf(Consumer c) throws IOException { + channel.basicConsume(QUEUE, true, c); + String serverQueue = channel.queueDeclare().getQueue(); + basicPublishVolatile("request".getBytes(), "", serverQueue, props()); + + GetResponse req = channel.basicGet(serverQueue, true); + return req.getProps().getReplyTo(); + } + + private AMQP.BasicProperties props() { + return MessageProperties.BASIC.builder().replyTo(QUEUE).build(); + } + + class QueueMessageConsumer extends DefaultConsumer { + + BlockingQueue messages = new LinkedBlockingQueue(); + + public QueueMessageConsumer(Channel channel) { + super(channel); + } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + messages.add(body); + } + + byte[] nextDelivery() { + return messages.poll(); + } + + byte[] nextDelivery(long timeoutInMs) throws InterruptedException { + return messages.poll(timeoutInMs, TimeUnit.MILLISECONDS); + } + + } +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/DoubleDeletion.java b/src/test/java/com/rabbitmq/client/test/functional/DoubleDeletion.java new file mode 100644 index 0000000000..a26bd4b227 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/DoubleDeletion.java @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.functional; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.test.BrokerTestCase; + +public class DoubleDeletion extends BrokerTestCase +{ + protected static final String Q = "DoubleDeletionQueue"; + protected static final String X = "DoubleDeletionExchange"; + + @Test public void doubleDeletionQueue() + throws IOException + { + channel.queueDelete(Q); + channel.queueDeclare(Q, false, false, false, null); + channel.queueDelete(Q); + channel.queueDelete(Q); + } + + @Test public void doubleDeletionExchange() + throws IOException + { + channel.exchangeDelete(X); + channel.exchangeDeclare(X, "direct"); + channel.exchangeDelete(X); + channel.exchangeDelete(X); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/DurableOnTransient.java b/src/test/java/com/rabbitmq/client/test/functional/DurableOnTransient.java new file mode 100644 index 0000000000..08508a71f3 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/DurableOnTransient.java @@ -0,0 +1,100 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.MessageProperties; + +public class DurableOnTransient extends ClusteredTestBase +{ + protected static final String Q = "SemiDurableBindings.DurableQueue"; + protected static final String X = "SemiDurableBindings.TransientExchange"; + + private GetResponse basicGet() + throws IOException + { + return channel.basicGet(Q, true); + } + + private void basicPublish() + throws IOException + { + channel.basicPublish(X, "", + MessageProperties.PERSISTENT_TEXT_PLAIN, + "persistent message".getBytes()); + } + + protected void createResources() throws IOException { + channel.exchangeDelete(X); + // transient exchange + channel.exchangeDeclare(X, "direct", false); + + channel.queueDelete(Q); + // durable queue + channel.queueDeclare(Q, true, false, false, null); + } + + protected void releaseResources() throws IOException { + channel.queueDelete(Q); + channel.exchangeDelete(X); + } + + @Test public void bindDurableToTransient() + throws IOException + { + channel.queueBind(Q, X, ""); + basicPublish(); + assertNotNull(basicGet()); + } + + @Test public void semiDurableBindingRemoval() throws IOException { + if (clusteredConnection != null) { + deleteExchange("x"); + declareTransientTopicExchange("x"); + clusteredChannel.queueDelete("q"); + clusteredChannel.queueDeclare("q", true, false, false, null); + channel.queueBind("q", "x", "k"); + + stopSecondary(); + boolean restarted = false; + try { + deleteExchange("x"); + + startSecondary(); + restarted = true; + + declareTransientTopicExchange("x"); + + basicPublishVolatile("x", "k"); + assertDelivered("q", 0); + + deleteQueue("q"); + deleteExchange("x"); + } finally { + if (!restarted) { + startSecondary(); + } + } + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/ExceptionHandling.java b/src/test/java/com/rabbitmq/client/test/functional/ExceptionHandling.java new file mode 100644 index 0000000000..ef71a94192 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/ExceptionHandling.java @@ -0,0 +1,107 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.test.TestUtils; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.Consumer; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.ExceptionHandler; +import com.rabbitmq.client.impl.DefaultExceptionHandler; +import com.rabbitmq.client.impl.ForgivingExceptionHandler; + +public class ExceptionHandling { + private ConnectionFactory newConnectionFactory(ExceptionHandler eh) { + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.setExceptionHandler(eh); + return cf; + } + + @Test public void defaultConsumerHandleConsumerException() throws IOException, InterruptedException, TimeoutException { + final CountDownLatch latch = new CountDownLatch(1); + final ExceptionHandler eh = new DefaultExceptionHandler() { + @Override + public void handleConsumerException(Channel channel, Throwable exception, Consumer consumer, String consumerTag, String methodName) { + super.handleConsumerException(channel, exception, consumer, consumerTag, methodName); + latch.countDown(); + } + }; + + testConsumerHandleConsumerException(eh, latch, true); + } + + @Test public void forgivingConsumerHandleConsumerException() throws IOException, InterruptedException, TimeoutException { + final CountDownLatch latch = new CountDownLatch(1); + final ExceptionHandler eh = new ForgivingExceptionHandler() { + @Override + public void handleConsumerException(Channel channel, Throwable exception, Consumer consumer, String consumerTag, String methodName) { + super.handleConsumerException(channel, exception, consumer, consumerTag, methodName); + latch.countDown(); + } + }; + + testConsumerHandleConsumerException(eh, latch, false); + } + + protected void testConsumerHandleConsumerException(ExceptionHandler eh, CountDownLatch latch, boolean expectChannelClose) + throws InterruptedException, TimeoutException, IOException { + ConnectionFactory cf = newConnectionFactory(eh); + assertEquals(cf.getExceptionHandler(), eh); + Connection conn = cf.newConnection(); + assertEquals(conn.getExceptionHandler(), eh); + Channel ch = conn.createChannel(); + String q = ch.queueDeclare().getQueue(); + ch.basicConsume(q, new DefaultConsumer(ch) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, + AMQP.BasicProperties properties, byte[] body) throws IOException { + throw new RuntimeException("exception expected here, don't freak out"); + } + }); + ch.basicPublish("", q, null, "".getBytes()); + wait(latch); + + assertEquals(!expectChannelClose, ch.isOpen()); + } + + @Test public void nullExceptionHandler() { + ConnectionFactory cf = TestUtils.connectionFactory(); + try { + cf.setExceptionHandler(null); + fail("expected setExceptionHandler to throw"); + } catch (IllegalArgumentException iae) { + // expected + } + } + + private void wait(CountDownLatch latch) throws InterruptedException { + latch.await(1800, TimeUnit.SECONDS); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/ExceptionMessages.java b/src/test/java/com/rabbitmq/client/test/functional/ExceptionMessages.java new file mode 100644 index 0000000000..1fd0051cde --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/ExceptionMessages.java @@ -0,0 +1,57 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AlreadyClosedException; +import com.rabbitmq.client.test.BrokerTestCase; + +public class ExceptionMessages extends BrokerTestCase { + @Test public void alreadyClosedExceptionMessageWithChannelError() throws IOException { + String uuid = UUID.randomUUID().toString(); + try { + channel.queueDeclarePassive(uuid); + fail("expected queueDeclarePassive to throw"); + } catch (IOException e) { + // ignored + } + + try { + channel.queueDeclarePassive(uuid); + fail("expected queueDeclarePassive to throw"); + } catch (AlreadyClosedException ace) { + assertTrue(ace.getMessage().startsWith("channel is already closed due to channel error")); + } + } + + @Test public void alreadyClosedExceptionMessageWithCleanClose() throws IOException { + String uuid = UUID.randomUUID().toString(); + + try { + channel.abort(); + channel.queueDeclare(uuid, false, false, false, null); + } catch (AlreadyClosedException ace) { + assertTrue(ace.getMessage().startsWith("channel is already closed due to clean channel shutdown")); + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/ExchangeDeclare.java b/src/test/java/com/rabbitmq/client/test/functional/ExchangeDeclare.java new file mode 100644 index 0000000000..da5769b4b6 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/ExchangeDeclare.java @@ -0,0 +1,127 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.test.TestUtils; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.BuiltinExchangeType; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; + +public class ExchangeDeclare extends ExchangeEquivalenceBase { + + static final String TYPE = "direct"; + + static final String NAME = "exchange_test"; + + public void releaseResources() throws IOException { + channel.exchangeDelete(NAME); + } + + @Test public void exchangeNoArgsEquivalence() throws IOException { + channel.exchangeDeclare(NAME, TYPE, false, false, null); + verifyEquivalent(NAME, TYPE, false, false, null); + } + + @Test public void singleLineFeedStrippedFromExchangeName() throws IOException { + channel.exchangeDeclare("exchange_test\n", TYPE, false, false, null); + verifyEquivalent(NAME, TYPE, false, false, null); + } + + @Test public void multipleLineFeedsStrippedFromExchangeName() throws IOException { + channel.exchangeDeclare("exchange\n_test\n", TYPE, false, false, null); + verifyEquivalent(NAME, TYPE, false, false, null); + } + + @Test public void multipleLineFeedAndCarriageReturnsStrippedFromExchangeName() throws IOException { + channel.exchangeDeclare("e\nxc\rhange\n\r_test\n\r", TYPE, false, false, null); + verifyEquivalent(NAME, TYPE, false, false, null); + } + + @Test public void exchangeNonsenseArgsEquivalent() throws IOException { + channel.exchangeDeclare(NAME, TYPE, false, false, null); + Map args = new HashMap(); + args.put("nonsensical-argument-surely-not-in-use", "foo"); + verifyEquivalent(NAME, TYPE, false, false, args); + } + + @Test public void exchangeDurableNotEquivalent() throws IOException { + channel.exchangeDeclare(NAME, TYPE, false, false, null); + verifyNotEquivalent(NAME, TYPE, true, false, null); + } + + @Test public void exchangeTypeNotEquivalent() throws IOException { + channel.exchangeDeclare(NAME, "direct", false, false, null); + verifyNotEquivalent(NAME, "fanout", false, false, null); + } + + @Test public void exchangeAutoDeleteNotEquivalent() throws IOException { + channel.exchangeDeclare(NAME, "direct", false, false, null); + verifyNotEquivalent(NAME, "direct", false, true, null); + } + + @Test public void exchangeDeclaredWithEnumerationEquivalentOnNonRecoverableConnection() throws IOException, InterruptedException { + doTestExchangeDeclaredWithEnumerationEquivalent(channel); + } + + @Test public void exchangeDeclaredWithEnumerationEquivalentOnRecoverableConnection() throws IOException, TimeoutException, InterruptedException { + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + connectionFactory.setAutomaticRecoveryEnabled(true); + connectionFactory.setTopologyRecoveryEnabled(false); + Connection c = connectionFactory.newConnection(); + try { + doTestExchangeDeclaredWithEnumerationEquivalent(c.createChannel()); + } finally { + c.abort(); + } + + } + + private void doTestExchangeDeclaredWithEnumerationEquivalent(Channel channel) throws IOException, InterruptedException { + assertEquals(4, BuiltinExchangeType.values().length, "There are 4 standard exchange types"); + for (BuiltinExchangeType exchangeType : BuiltinExchangeType.values()) { + channel.exchangeDeclare(NAME, exchangeType); + verifyEquivalent(NAME, exchangeType.getType(), false, false, null); + channel.exchangeDelete(NAME); + + channel.exchangeDeclare(NAME, exchangeType, false); + verifyEquivalent(NAME, exchangeType.getType(), false, false, null); + channel.exchangeDelete(NAME); + + channel.exchangeDeclare(NAME, exchangeType, false, false, null); + verifyEquivalent(NAME, exchangeType.getType(), false, false, null); + channel.exchangeDelete(NAME); + + channel.exchangeDeclare(NAME, exchangeType, false, false, false, null); + verifyEquivalent(NAME, exchangeType.getType(), false, false, null); + channel.exchangeDelete(NAME); + + channel.exchangeDeclareNoWait(NAME, exchangeType, false, false, false, null); + // no check, this one is asynchronous + channel.exchangeDelete(NAME); + } + } +} diff --git a/test/src/com/rabbitmq/client/test/functional/ExchangeDeleteIfUnused.java b/src/test/java/com/rabbitmq/client/test/functional/ExchangeDeleteIfUnused.java similarity index 55% rename from test/src/com/rabbitmq/client/test/functional/ExchangeDeleteIfUnused.java rename to src/test/java/com/rabbitmq/client/test/functional/ExchangeDeleteIfUnused.java index c361b2a2dd..ddd324420f 100644 --- a/test/src/com/rabbitmq/client/test/functional/ExchangeDeleteIfUnused.java +++ b/src/test/java/com/rabbitmq/client/test/functional/ExchangeDeleteIfUnused.java @@ -1,23 +1,27 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.test.BrokerTestCase; @@ -29,8 +33,7 @@ public class ExchangeDeleteIfUnused extends BrokerTestCase { private final static String ROUTING_KEY = "something"; protected void createResources() - throws IOException - { + throws IOException, TimeoutException { super.createResources(); channel.exchangeDeclare(EXCHANGE_NAME, "direct"); String queueName = channel.queueDeclare().getQueue(); @@ -46,7 +49,7 @@ protected void releaseResources() /* Attempt to Exchange.Delete(ifUnused = true) a used exchange. * Should throw an exception. */ - public void testExchangeDelete() { + @Test public void exchangeDelete() { try { channel.exchangeDelete(EXCHANGE_NAME, true); fail("Exception expected if exchange in use"); diff --git a/src/test/java/com/rabbitmq/client/test/functional/ExchangeDeletePredeclared.java b/src/test/java/com/rabbitmq/client/test/functional/ExchangeDeletePredeclared.java new file mode 100644 index 0000000000..e7d2acbc3d --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/ExchangeDeletePredeclared.java @@ -0,0 +1,39 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.test.BrokerTestCase; + +import java.io.IOException; + +public class ExchangeDeletePredeclared extends BrokerTestCase { + public void testDeletingPredeclaredAmqExchange() throws IOException { + try { + channel.exchangeDelete("amq.fanout"); + } catch (IOException e) { + checkShutdownSignal(AMQP.ACCESS_REFUSED, e); + } + } + + public void testDeletingPredeclaredAmqRabbitMQExchange() throws IOException { + try { + channel.exchangeDelete("amq.rabbitmq.log"); + } catch (IOException e) { + checkShutdownSignal(AMQP.ACCESS_REFUSED, e); + } + } +} diff --git a/test/src/com/rabbitmq/client/test/functional/ExchangeEquivalenceBase.java b/src/test/java/com/rabbitmq/client/test/functional/ExchangeEquivalenceBase.java similarity index 56% rename from test/src/com/rabbitmq/client/test/functional/ExchangeEquivalenceBase.java rename to src/test/java/com/rabbitmq/client/test/functional/ExchangeEquivalenceBase.java index dae6c6c38a..a83477d2eb 100644 --- a/test/src/com/rabbitmq/client/test/functional/ExchangeEquivalenceBase.java +++ b/src/test/java/com/rabbitmq/client/test/functional/ExchangeEquivalenceBase.java @@ -1,27 +1,28 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.test.BrokerTestCase; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.util.Map; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.test.BrokerTestCase; + public abstract class ExchangeEquivalenceBase extends BrokerTestCase { public void verifyEquivalent(String name, String type, boolean durable, boolean autoDelete, diff --git a/test/src/com/rabbitmq/client/test/functional/ExchangeExchangeBindings.java b/src/test/java/com/rabbitmq/client/test/functional/ExchangeExchangeBindings.java similarity index 82% rename from test/src/com/rabbitmq/client/test/functional/ExchangeExchangeBindings.java rename to src/test/java/com/rabbitmq/client/test/functional/ExchangeExchangeBindings.java index b6206bb785..5668448d25 100644 --- a/test/src/com/rabbitmq/client/test/functional/ExchangeExchangeBindings.java +++ b/src/test/java/com/rabbitmq/client/test/functional/ExchangeExchangeBindings.java @@ -1,28 +1,31 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import java.io.IOException; -import com.rabbitmq.client.AMQP; +import org.junit.jupiter.api.Test; + import com.rabbitmq.client.QueueingConsumer; -import com.rabbitmq.client.ShutdownSignalException; import com.rabbitmq.client.QueueingConsumer.Delivery; +import com.rabbitmq.client.ShutdownSignalException; import com.rabbitmq.client.test.BrokerTestCase; public class ExchangeExchangeBindings extends BrokerTestCase { @@ -79,7 +82,7 @@ protected void consumeNoDuplicates(QueueingConsumer consumer) assertEquals(new String(MARKER), new String(markerDelivery.getBody())); } - public void testBindingCreationDeletion() throws IOException { + @Test public void bindingCreationDeletion() throws IOException { channel.exchangeUnbind("e2", "e1", ""); channel.exchangeBind("e2", "e1", ""); channel.exchangeBind("e2", "e1", ""); @@ -94,7 +97,7 @@ public void testBindingCreationDeletion() throws IOException { * add binding (e2 --> e1) * test (e2 --> {q2, q1, q0}) */ - public void testSimpleChains() throws IOException, ShutdownSignalException, + @Test public void simpleChains() throws IOException, ShutdownSignalException, InterruptedException { publishWithMarker("e0", ""); consumeNoDuplicates(consumers[0]); @@ -121,7 +124,7 @@ public void testSimpleChains() throws IOException, ShutdownSignalException, * resulting in: (e1 --> {q1, e0 --> {q0, q1}}) * test (e1 --> {q0, q1}) */ - public void testDuplicateQueueDestinations() throws IOException, + @Test public void duplicateQueueDestinations() throws IOException, ShutdownSignalException, InterruptedException { channel.queueBind("q1", "e0", ""); publishWithMarker("e0", ""); @@ -143,7 +146,7 @@ public void testDuplicateQueueDestinations() throws IOException, * add binding (e0 --> e2) * test (eN --> {q0, q1, q2}) for N in [0..2] */ - public void testExchangeRoutingLoop() throws IOException, + @Test public void exchangeRoutingLoop() throws IOException, ShutdownSignalException, InterruptedException { channel.exchangeBind("e0", "e1", ""); channel.exchangeBind("e1", "e2", ""); @@ -172,7 +175,7 @@ public void testExchangeRoutingLoop() throws IOException, * Then remove the first set of bindings from e --> eN for N in [0..2] * test publish with rk to e */ - public void testTopicExchange() throws IOException, ShutdownSignalException, + @Test public void topicExchange() throws IOException, ShutdownSignalException, InterruptedException { channel.exchangeDeclare("e", "topic"); diff --git a/test/src/com/rabbitmq/client/test/functional/ExchangeExchangeBindingsAutoDelete.java b/src/test/java/com/rabbitmq/client/test/functional/ExchangeExchangeBindingsAutoDelete.java similarity index 72% rename from test/src/com/rabbitmq/client/test/functional/ExchangeExchangeBindingsAutoDelete.java rename to src/test/java/com/rabbitmq/client/test/functional/ExchangeExchangeBindingsAutoDelete.java index 36cd989d45..e78c25f663 100644 --- a/test/src/com/rabbitmq/client/test/functional/ExchangeExchangeBindingsAutoDelete.java +++ b/src/test/java/com/rabbitmq/client/test/functional/ExchangeExchangeBindingsAutoDelete.java @@ -1,24 +1,27 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.IOException; +import org.junit.jupiter.api.Test; + import com.rabbitmq.client.AMQP; import com.rabbitmq.client.test.BrokerTestCase; @@ -49,7 +52,7 @@ protected void assertExchangeNotExists(String name) throws IOException { * build (A -> B) and (B -> A) and then delete one binding and both * exchanges should autodelete */ - public void testAutoDeleteExchangesSimpleLoop() throws IOException { + @Test public void autoDeleteExchangesSimpleLoop() throws IOException { String[] exchanges = new String[] {"A", "B"}; declareExchanges(exchanges); channel.exchangeBind("A", "B", ""); @@ -62,7 +65,7 @@ public void testAutoDeleteExchangesSimpleLoop() throws IOException { /* * build (A -> B) (B -> C) (C -> D) and then delete D. All should autodelete */ - public void testTransientAutoDelete() throws IOException { + @Test public void transientAutoDelete() throws IOException { String[] exchanges = new String[] {"A", "B", "C", "D"}; declareExchanges(exchanges); channel.exchangeBind("B", "A", ""); @@ -77,7 +80,7 @@ public void testTransientAutoDelete() throws IOException { * build (A -> B) (B -> C) (C -> D) (Source -> A) (Source -> B) (Source -> * C) (Source -> D) On removal of D, all should autodelete */ - public void testRepeatedTargetAutoDelete() throws IOException { + @Test public void repeatedTargetAutoDelete() throws IOException { String[] exchanges = new String[] {"A", "B", "C", "D"}; declareExchanges(exchanges); channel.exchangeDeclare("Source", "fanout", false, true, null); @@ -103,7 +106,7 @@ public void testRepeatedTargetAutoDelete() throws IOException { /* * build (A -> B) (B -> C) (A -> C). Delete C and they should all vanish */ - public void testAutoDeleteBindingToVanishedExchange() throws IOException { + @Test public void autoDeleteBindingToVanishedExchange() throws IOException { String[] exchanges = new String[] {"A", "B", "C"}; declareExchanges(exchanges); channel.exchangeBind("C", "B", ""); diff --git a/src/test/java/com/rabbitmq/client/test/functional/FrameMax.java b/src/test/java/com/rabbitmq/client/test/functional/FrameMax.java new file mode 100644 index 0000000000..1bc007d75b --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/FrameMax.java @@ -0,0 +1,273 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.impl.AMQBasicProperties; +import com.rabbitmq.client.test.TestUtils; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Address; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.impl.AMQCommand; +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.ContentHeaderPropertyWriter; +import com.rabbitmq.client.impl.Frame; +import com.rabbitmq.client.impl.FrameHandler; +import com.rabbitmq.client.impl.LongStringHelper; +import com.rabbitmq.client.impl.SocketFrameHandler; +import com.rabbitmq.client.test.BrokerTestCase; + +public class FrameMax extends BrokerTestCase { + /* This value for FrameMax is larger than the minimum and less + * than what Rabbit suggests. */ + final static int FRAME_MAX = 70000; + final static int REAL_FRAME_MAX = FRAME_MAX - 8; + + public FrameMax() { + connectionFactory = new MyConnectionFactory(); + connectionFactory.setRequestedFrameMax(FRAME_MAX); + } + + @Test public void negotiationOk() { + assertEquals(FRAME_MAX, connection.getFrameMax()); + } + + /* Publish a message of size FRAME_MAX. The broker should split + * this into two frames before sending back. Frame content should + * be less or equal to frame-max - 8. */ + @Test public void frameSizes() + throws IOException, InterruptedException + { + String queueName = channel.queueDeclare().getQueue(); + /* This should result in at least 3 frames. */ + int howMuch = 2*FRAME_MAX; + basicPublishVolatile(new byte[howMuch], queueName); + /* Receive everything that was sent out. */ + while (howMuch > 0) { + try { + GetResponse response = channel.basicGet(queueName, false); + howMuch -= response.getBody().length; + } catch (Exception e) { + e.printStackTrace(); + fail("Exception in basicGet loop: " + e); + } + } + } + + /* server should reject frames larger than AMQP.FRAME_MIN_SIZE + * during connection negotiation */ + @Test public void rejectLargeFramesDuringConnectionNegotiation() + throws IOException, TimeoutException { + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.getClientProperties().put("too_long", LongStringHelper.asLongString(new byte[AMQP.FRAME_MIN_SIZE])); + try { + cf.newConnection(); + fail("Expected exception during connection negotiation"); + } catch (IOException e) { + } + } + + /* server should reject frames larger than the negotiated frame + * size */ + @Test public void rejectExceedingFrameMax() + throws IOException, TimeoutException { + closeChannel(); + closeConnection(); + ConnectionFactory cf = new GenerousConnectionFactory(); + cf.setRequestedFrameMax(8192); + connection = cf.newConnection(); + openChannel(); + basicPublishVolatile(new byte[connection.getFrameMax() * 2], "void"); + expectError(AMQP.FRAME_ERROR); + } + + /* client should throw exception if headers exceed negotiated + * frame size */ + @Test public void rejectHeadersExceedingFrameMax() + throws IOException, TimeoutException { + declareTransientTopicExchange("x"); + String queueName = channel.queueDeclare().getQueue(); + channel.queueBind(queueName, "x", "foobar"); + + Map headers = new HashMap(); + String headerName = "x-huge-header"; + + // create headers with zero-length value to calculate maximum header value size before exceeding frame_max + headers.put(headerName, LongStringHelper.asLongString(new byte[0])); + AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() + .headers(headers) + .build(); + Frame minimalHeaderFrame = properties.toFrame(0, 0); + int maxHeaderValueSize = FRAME_MAX - minimalHeaderFrame.size(); + + // create headers with maximum header value size (frame size equals frame_max) + headers.put(headerName, LongStringHelper.asLongString(new byte[maxHeaderValueSize])); + properties = new AMQP.BasicProperties.Builder().headers(headers).build(); + + basicPublishVolatile(new byte[100], "x", "foobar", properties); + assertDelivered(queueName, 1); + + // create headers with frame size exceeding frame_max by 1 + headers.put(headerName, LongStringHelper.asLongString(new byte[maxHeaderValueSize + 1])); + properties = new AMQP.BasicProperties.Builder().headers(headers).build(); + try { + basicPublishVolatile(new byte[100], "x", "foobar", properties); + fail("expected rejectHeadersExceedingFrameMax to throw"); + } catch (IllegalArgumentException iae) { + assertTrue(iae.getMessage().startsWith("Content headers exceeded max frame size")); + // check that the channel is still operational + assertDelivered(queueName, 0); + } + + // cleanup + deleteExchange("x"); + } + + + // see rabbitmq/rabbitmq-java-client#407 + @Test public void unlimitedFrameMaxWithHeaders() + throws IOException, TimeoutException { + closeChannel(); + closeConnection(); + ConnectionFactory cf = newConnectionFactory(); + cf.setRequestedFrameMax(0); + connection = cf.newConnection(); + openChannel(); + + Map headers = new HashMap(); + headers.put("h1", LongStringHelper.asLongString(new byte[250])); + headers.put("h1", LongStringHelper.asLongString(new byte[500])); + headers.put("h1", LongStringHelper.asLongString(new byte[750])); + headers.put("h1", LongStringHelper.asLongString(new byte[5000])); + headers.put("h1", LongStringHelper.asLongString(new byte[50000])); + AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() + .headers(headers) + .build(); + basicPublishVolatile(new byte[500000], "", "", properties); + } + + @Override + protected boolean isAutomaticRecoveryEnabled() { + return false; + } + + /* ConnectionFactory that uses MyFrameHandler rather than + * SocketFrameHandler. */ + private static class MyConnectionFactory extends ConnectionFactory { + + public MyConnectionFactory() { + super(); + if(TestUtils.USE_NIO) { + useNio(); + } else { + useBlockingIo(); + } + } + + protected FrameHandler createFrameHandler(Socket sock) + throws IOException + { + return new MyFrameHandler(sock); + } + } + + /* FrameHandler with added frame-max error checking. */ + private static class MyFrameHandler extends SocketFrameHandler { + public MyFrameHandler(Socket socket) + throws IOException + { + super(socket); + } + + public Frame readFrame() throws IOException { + Frame f = super.readFrame(); + int size = f.getPayload().length; + if (size > REAL_FRAME_MAX) + fail("Received frame of size " + size + + ", which exceeds " + REAL_FRAME_MAX + "."); + //System.out.printf("Received a frame of size %d.\n", f.getPayload().length); + return f; + } + } + + /* + AMQConnection with a frame_max that is one higher than what it + tells the server. + */ + private static class GenerousAMQConnection extends AMQConnection { + + public GenerousAMQConnection(ConnectionFactory factory, + FrameHandler handler, + ExecutorService executor) { + super(factory.params(executor), handler); + } + + @Override public int getFrameMax() { + // the RabbitMQ broker permits frames that are oversize by + // up to EMPTY_FRAME_SIZE octets + return super.getFrameMax() + AMQCommand.EMPTY_FRAME_SIZE + 1; + } + + } + + private static class GenerousConnectionFactory extends ConnectionFactory { + + public GenerousConnectionFactory() { + super(); + if(TestUtils.USE_NIO) { + useNio(); + } else { + useBlockingIo(); + } + } + + @Override public Connection newConnection(ExecutorService executor, List
addrs) + throws IOException, TimeoutException { + IOException lastException = null; + for (Address addr : addrs) { + try { + FrameHandler frameHandler = createFrameHandlerFactory().create(addr, null); + AMQConnection conn = new GenerousAMQConnection(this, frameHandler, executor); + conn.start(); + return conn; + } catch (IOException e) { + lastException = e; + } + } + throw (lastException != null) ? lastException + : new IOException("failed to connect"); + } + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/FunctionalTestSuite.java b/src/test/java/com/rabbitmq/client/test/functional/FunctionalTestSuite.java new file mode 100644 index 0000000000..820728ce01 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/FunctionalTestSuite.java @@ -0,0 +1,85 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.functional; + +import com.rabbitmq.client.impl.WorkPoolTests; +import com.rabbitmq.client.test.Bug20004Test; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectClasses({ + ConnectionOpen.class, + Heartbeat.class, + Tables.class, + DoubleDeletion.class, + Routing.class, + BindingLifecycle.class, + Recover.class, + Reject.class, + Transactions.class, + RequeueOnConnectionClose.class, + RequeueOnChannelClose.class, + DurableOnTransient.class, + NoRequeueOnCancel.class, + Bug20004Test.class, + ExchangeDeleteIfUnused.class, + QosTests.class, + AlternateExchange.class, + ExchangeExchangeBindings.class, + ExchangeExchangeBindingsAutoDelete.class, + ExchangeDeclare.class, + FrameMax.class, + QueueLifecycle.class, + QueueLease.class, + QueueExclusivity.class, + QueueSizeLimit.class, + InvalidAcks.class, + InvalidAcksTx.class, + DefaultExchange.class, + UnbindAutoDeleteExchange.class, + Confirm.class, + ConsumerCancelNotification.class, + UnexpectedFrames.class, + PerQueueTTL.class, + PerMessageTTL.class, + PerQueueVsPerMessageTTL.class, + DeadLetterExchange.class, + SaslMechanisms.class, + UserIDHeader.class, + InternalExchange.class, + CcRoutes.class, + WorkPoolTests.class, + HeadersExchangeValidation.class, + ConsumerPriorities.class, + Policies.class, + ConnectionRecovery.class, + ExceptionHandling.class, + PerConsumerPrefetch.class, + DirectReplyTo.class, + ConsumerCount.class, + BasicGet.class, + Nack.class, + ExceptionMessages.class, + Metrics.class, + MicrometerObservationCollectorMetrics.class, + TopologyRecoveryFiltering.class, + TopologyRecoveryRetry.class +}) +public class FunctionalTestSuite { + +} diff --git a/test/src/com/rabbitmq/client/test/functional/HeadersExchangeValidation.java b/src/test/java/com/rabbitmq/client/test/functional/HeadersExchangeValidation.java similarity index 52% rename from test/src/com/rabbitmq/client/test/functional/HeadersExchangeValidation.java rename to src/test/java/com/rabbitmq/client/test/functional/HeadersExchangeValidation.java index 17724034aa..c7bff761ee 100644 --- a/test/src/com/rabbitmq/client/test/functional/HeadersExchangeValidation.java +++ b/src/test/java/com/rabbitmq/client/test/functional/HeadersExchangeValidation.java @@ -1,15 +1,35 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.test.BrokerTestCase; +import static org.junit.jupiter.api.Assertions.fail; +import com.rabbitmq.client.test.TestUtils; import java.io.IOException; import java.util.HashMap; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.test.BrokerTestCase; + public class HeadersExchangeValidation extends BrokerTestCase { - public void testHeadersValidation() throws IOException + @Test public void headersValidation() throws IOException { AMQP.Queue.DeclareOk ok = channel.queueDeclare(); String queue = ok.getQueue(); @@ -28,6 +48,14 @@ public void testHeadersValidation() throws IOException arguments.put("x-match", "any"); succeedBind(queue, arguments); + + if (TestUtils.isVersion310orLater(connection)) { + arguments.put("x-match", "all-with-x"); + succeedBind(queue, arguments); + + arguments.put("x-match", "any-with-x"); + succeedBind(queue, arguments); + } } private void failBind(String queue, HashMap arguments) { @@ -43,6 +71,6 @@ private void failBind(String queue, HashMap arguments) { private void succeedBind(String queue, HashMap arguments) throws IOException { Channel ch = connection.createChannel(); ch.queueBind(queue, "amq.headers", "", arguments); - ch.close(); + ch.abort(); } } diff --git a/src/test/java/com/rabbitmq/client/test/functional/Heartbeat.java b/src/test/java/com/rabbitmq/client/test/functional/Heartbeat.java new file mode 100644 index 0000000000..7cdb1e8736 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/Heartbeat.java @@ -0,0 +1,49 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.functional; + +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; +import com.rabbitmq.client.test.BrokerTestCase; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +public class Heartbeat extends BrokerTestCase { + + @Override + protected ConnectionFactory newConnectionFactory() { + ConnectionFactory cf = super.newConnectionFactory(); + cf.setRequestedHeartbeat(1); + return cf; + } + + @Test + public void heartbeat() throws InterruptedException { + assertEquals(1, connection.getHeartbeat()); + Thread.sleep(3100); + assertTrue(connection.isOpen()); + ((AutorecoveringConnection) connection).getDelegate().setHeartbeat(0); + assertEquals(0, connection.getHeartbeat()); + Thread.sleep(3100); + assertFalse(connection.isOpen()); + + } + +} diff --git a/test/src/com/rabbitmq/client/test/functional/InternalExchange.java b/src/test/java/com/rabbitmq/client/test/functional/InternalExchange.java similarity index 73% rename from test/src/com/rabbitmq/client/test/functional/InternalExchange.java rename to src/test/java/com/rabbitmq/client/test/functional/InternalExchange.java index 295ec83415..83d4d83689 100644 --- a/test/src/com/rabbitmq/client/test/functional/InternalExchange.java +++ b/src/test/java/com/rabbitmq/client/test/functional/InternalExchange.java @@ -1,25 +1,28 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.io.IOException; import java.util.Arrays; +import org.junit.jupiter.api.Test; + import com.rabbitmq.client.AMQP; import com.rabbitmq.client.GetResponse; import com.rabbitmq.client.test.BrokerTestCase; @@ -70,7 +73,7 @@ protected void createResources() throws IOException } - public void testTryPublishingToInternalExchange() + @Test public void tryPublishingToInternalExchange() throws IOException { byte[] testDataBody = "test-data".getBytes(); diff --git a/src/test/java/com/rabbitmq/client/test/functional/InvalidAcks.java b/src/test/java/com/rabbitmq/client/test/functional/InvalidAcks.java new file mode 100644 index 0000000000..e9e7cf96ef --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/InvalidAcks.java @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import java.io.IOException; + +public class InvalidAcks extends InvalidAcksBase { + protected void select() throws IOException {} + protected void commit() throws IOException {} +} + diff --git a/test/src/com/rabbitmq/client/test/functional/InvalidAcksBase.java b/src/test/java/com/rabbitmq/client/test/functional/InvalidAcksBase.java similarity index 55% rename from test/src/com/rabbitmq/client/test/functional/InvalidAcksBase.java rename to src/test/java/com/rabbitmq/client/test/functional/InvalidAcksBase.java index fcd7f6cb76..2df0d4f551 100644 --- a/test/src/com/rabbitmq/client/test/functional/InvalidAcksBase.java +++ b/src/test/java/com/rabbitmq/client/test/functional/InvalidAcksBase.java @@ -1,18 +1,17 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; @@ -21,6 +20,8 @@ import java.io.IOException; +import org.junit.jupiter.api.Test; + /** * See bug 21846: * Basic.Ack is now required to signal a channel error immediately upon @@ -32,7 +33,7 @@ public abstract class InvalidAcksBase extends BrokerTestCase { protected abstract void select() throws IOException; protected abstract void commit() throws IOException; - public void testDoubleAck() + @Test public void doubleAck() throws IOException { select(); @@ -47,7 +48,7 @@ public void testDoubleAck() expectError(AMQP.PRECONDITION_FAILED); } - public void testCrazyAck() + @Test public void crazyAck() throws IOException { select(); diff --git a/src/test/java/com/rabbitmq/client/test/functional/InvalidAcksTx.java b/src/test/java/com/rabbitmq/client/test/functional/InvalidAcksTx.java new file mode 100644 index 0000000000..ad7d9f4c5f --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/InvalidAcksTx.java @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import java.io.IOException; + +public class InvalidAcksTx extends InvalidAcksBase { + protected void select() throws IOException { + channel.txSelect(); + } + + protected void commit() throws IOException { + channel.txCommit(); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/MessageCount.java b/src/test/java/com/rabbitmq/client/test/functional/MessageCount.java new file mode 100644 index 0000000000..3712316af1 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/MessageCount.java @@ -0,0 +1,40 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.test.BrokerTestCase; + +public class MessageCount extends BrokerTestCase { + @Test public void messageCount() throws IOException { + String q = generateQueueName(); + channel.queueDeclare(q, false, true, false, null); + assertEquals(0, channel.messageCount(q)); + + basicPublishVolatile(q); + assertEquals(1, channel.messageCount(q)); + basicPublishVolatile(q); + assertEquals(2, channel.messageCount(q)); + + channel.queuePurge(q); + assertEquals(0, channel.messageCount(q)); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/Metrics.java b/src/test/java/com/rabbitmq/client/test/functional/Metrics.java new file mode 100644 index 0000000000..8531380140 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/Metrics.java @@ -0,0 +1,672 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.MessageProperties; +import com.rabbitmq.client.Recoverable; +import com.rabbitmq.client.RecoveryListener; +import com.rabbitmq.client.impl.StandardMetricsCollector; +import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.client.test.TestUtils; +import com.rabbitmq.tools.Host; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static com.rabbitmq.client.test.TestUtils.waitAtMost; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + */ +public class Metrics extends BrokerTestCase { + + public static Object[] data() { + return new Object[] { createConnectionFactory(), createAutoRecoveryConnectionFactory() }; + } + + static final String QUEUE = "metrics.queue"; + + @Override + protected void createResources() throws IOException { + channel.queueDeclare(QUEUE, true, false, false, null); + } + + @Override + protected void releaseResources() throws IOException { + channel.queueDelete(QUEUE); + } + + @ParameterizedTest + @MethodSource("data") + public void metrics(ConnectionFactory connectionFactory) throws IOException, TimeoutException { + StandardMetricsCollector metrics = new StandardMetricsCollector(); + connectionFactory.setMetricsCollector(metrics); + Connection connection1 = null; + Connection connection2 = null; + try { + connection1 = connectionFactory.newConnection(); + assertThat(metrics.getConnections().getCount()).isEqualTo(1L); + + connection1.createChannel(); + connection1.createChannel(); + Channel channel = connection1.createChannel(); + assertThat(metrics.getChannels().getCount()).isEqualTo(3L); + + sendMessage(channel); + assertThat(metrics.getPublishedMessages().getCount()).isEqualTo(1L); + sendMessage(channel); + assertThat(metrics.getPublishedMessages().getCount()).isEqualTo(2L); + + channel.basicGet(QUEUE, true); + assertThat(metrics.getConsumedMessages().getCount()).isEqualTo(1L); + channel.basicGet(QUEUE, true); + assertThat(metrics.getConsumedMessages().getCount()).isEqualTo(2L); + channel.basicGet(QUEUE, true); + assertThat(metrics.getConsumedMessages().getCount()).isEqualTo(2L); + + connection2 = connectionFactory.newConnection(); + assertThat(metrics.getConnections().getCount()).isEqualTo(2L); + + connection2.createChannel(); + channel = connection2.createChannel(); + assertThat(metrics.getChannels().getCount()).isEqualTo(3L+2L); + sendMessage(channel); + sendMessage(channel); + assertThat(metrics.getPublishedMessages().getCount()).isEqualTo(2L+2L); + + channel.basicGet(QUEUE, true); + assertThat(metrics.getConsumedMessages().getCount()).isEqualTo(2L+1L); + + channel.basicConsume(QUEUE, true, new DefaultConsumer(channel)); + waitAtMost(timeout(), () -> metrics.getConsumedMessages().getCount() == 2L+1L+1L); + + safeClose(connection1); + waitAtMost(timeout(), () -> metrics.getConnections().getCount() == 1L); + waitAtMost(timeout(), () -> metrics.getChannels().getCount() == 2L); + + safeClose(connection2); + waitAtMost(timeout(), () -> metrics.getConnections().getCount() == 0L); + waitAtMost(timeout(), () -> metrics.getChannels().getCount() == 0L); + + assertThat(metrics.getAcknowledgedMessages().getCount()).isEqualTo(0L); + assertThat(metrics.getRejectedMessages().getCount()).isEqualTo(0L); + + } finally { + safeClose(connection1); + safeClose(connection2); + } + } + + @ParameterizedTest + @MethodSource("data") + public void metricsPublisherUnrouted(ConnectionFactory connectionFactory) throws IOException, TimeoutException { + StandardMetricsCollector metrics = new StandardMetricsCollector(); + connectionFactory.setMetricsCollector(metrics); + Connection connection = null; + try { + connection = connectionFactory.newConnection(); + Channel channel = connection.createChannel(); + channel.confirmSelect(); + assertThat(metrics.getPublishUnroutedMessages().getCount()).isEqualTo(0L); + // when + channel.basicPublish( + "amq.direct", + "any-unroutable-routing-key", + /* basic.return will be sent back only if the message is mandatory */ true, + MessageProperties.MINIMAL_BASIC, + "any-message".getBytes() + ); + // then + waitAtMost(timeout(), () -> metrics.getPublishUnroutedMessages().getCount() == 1L); + } finally { + safeClose(connection); + } + } + + @ParameterizedTest + @MethodSource("data") + public void metricsPublisherAck(ConnectionFactory connectionFactory) throws IOException, TimeoutException, InterruptedException { + StandardMetricsCollector metrics = new StandardMetricsCollector(); + connectionFactory.setMetricsCollector(metrics); + Connection connection = null; + try { + connection = connectionFactory.newConnection(); + Channel channel = connection.createChannel(); + channel.confirmSelect(); + assertThat(metrics.getPublishAcknowledgedMessages().getCount()).isEqualTo(0L); + MultipleAckConsumer consumer = new MultipleAckConsumer(channel, false); + channel.basicConsume(QUEUE, false, consumer); + CountDownLatch confirmedLatch = new CountDownLatch(1); + channel.addConfirmListener((deliveryTag, multiple) -> confirmedLatch.countDown(), + (deliveryTag, multiple) -> { }); + // when + sendMessage(channel); + assertThat(confirmedLatch.await(5, TimeUnit.SECONDS)).isTrue(); + // then + waitAtMost(Duration.ofSeconds(5), () -> metrics.getPublishAcknowledgedMessages().getCount() == 1); + waitAtMost(() -> consumer.ackedCount() == 1); + } finally { + safeClose(connection); + } + } + + @ParameterizedTest + @MethodSource("data") + public void metricsAck(ConnectionFactory connectionFactory) throws IOException, TimeoutException { + StandardMetricsCollector metrics = new StandardMetricsCollector(); + connectionFactory.setMetricsCollector(metrics); + + Connection connection = null; + try { + connection = connectionFactory.newConnection(); + Channel channel1 = connection.createChannel(); + Channel channel2 = connection.createChannel(); + + sendMessage(channel1); + GetResponse getResponse = channel1.basicGet(QUEUE, false); + channel1.basicAck(getResponse.getEnvelope().getDeliveryTag(), false); + assertThat(metrics.getConsumedMessages().getCount()).isEqualTo(1L); + assertThat(metrics.getAcknowledgedMessages().getCount()).isEqualTo(1L); + + // basicGet / basicAck + sendMessage(channel1); + sendMessage(channel2); + sendMessage(channel1); + sendMessage(channel2); + sendMessage(channel1); + sendMessage(channel2); + + GetResponse response1 = channel1.basicGet(QUEUE, false); + GetResponse response2 = channel2.basicGet(QUEUE, false); + GetResponse response3 = channel1.basicGet(QUEUE, false); + GetResponse response4 = channel2.basicGet(QUEUE, false); + GetResponse response5 = channel1.basicGet(QUEUE, false); + GetResponse response6 = channel2.basicGet(QUEUE, false); + + assertThat(metrics.getConsumedMessages().getCount()).isEqualTo(1L+6L); + assertThat(metrics.getAcknowledgedMessages().getCount()).isEqualTo(1L); + + channel1.basicAck(response5.getEnvelope().getDeliveryTag(), false); + assertThat(metrics.getAcknowledgedMessages().getCount()).isEqualTo(1L+1L); + channel1.basicAck(response3.getEnvelope().getDeliveryTag(), true); + assertThat(metrics.getAcknowledgedMessages().getCount()).isEqualTo(1L+1L+2L); + + channel2.basicAck(response2.getEnvelope().getDeliveryTag(), true); + assertThat(metrics.getAcknowledgedMessages().getCount()).isEqualTo(1L+(1L+2L)+1L); + channel2.basicAck(response6.getEnvelope().getDeliveryTag(), true); + assertThat(metrics.getAcknowledgedMessages().getCount()).isEqualTo(1L+(1L+2L)+1L+2L); + + long alreadySentMessages = 1+(1+2)+1+2; + + // basicConsume / basicAck + channel1.basicConsume(QUEUE, false, new MultipleAckConsumer(channel1, false)); + channel1.basicConsume(QUEUE, false, new MultipleAckConsumer(channel1, true)); + channel2.basicConsume(QUEUE, false, new MultipleAckConsumer(channel2, false)); + channel2.basicConsume(QUEUE, false, new MultipleAckConsumer(channel2, true)); + + int nbMessages = 10; + for(int i = 0; i < nbMessages; i++) { + sendMessage(i%2 == 0 ? channel1 : channel2); + } + + waitAtMost(timeout(), () -> metrics.getConsumedMessages().getCount() == alreadySentMessages+nbMessages); + + waitAtMost(timeout(), () -> metrics.getAcknowledgedMessages().getCount() == alreadySentMessages+nbMessages); + + } finally { + safeClose(connection); + } + } + + @ParameterizedTest + @MethodSource("data") + public void metricsReject(ConnectionFactory connectionFactory) throws IOException, TimeoutException { + StandardMetricsCollector metrics = new StandardMetricsCollector(); + connectionFactory.setMetricsCollector(metrics); + + Connection connection = null; + try { + connection = connectionFactory.newConnection(); + Channel channel = connection.createChannel(); + + sendMessage(channel); + sendMessage(channel); + sendMessage(channel); + + GetResponse response1 = channel.basicGet(QUEUE, false); + GetResponse response2 = channel.basicGet(QUEUE, false); + GetResponse response3 = channel.basicGet(QUEUE, false); + + channel.basicReject(response2.getEnvelope().getDeliveryTag(), false); + assertThat(metrics.getRejectedMessages().getCount()).isEqualTo(1L); + + channel.basicNack(response3.getEnvelope().getDeliveryTag(), true, false); + assertThat(metrics.getRejectedMessages().getCount()).isEqualTo(1L+2L); + } finally { + safeClose(connection); + } + } + + @ParameterizedTest + @MethodSource("data") + public void multiThreadedMetricsStandardConnection(ConnectionFactory connectionFactory) throws InterruptedException, TimeoutException, IOException { + StandardMetricsCollector metrics = new StandardMetricsCollector(); + connectionFactory.setMetricsCollector(metrics); + int nbConnections = 3; + int nbChannelsPerConnection = 5; + int nbChannels = nbConnections * nbChannelsPerConnection; + long nbOfMessages = 100; + int nbTasks = nbChannels; // channel are not thread-safe + + Random random = new Random(); + + // create connections + Connection [] connections = new Connection[nbConnections]; + ExecutorService executorService = Executors.newFixedThreadPool(nbTasks); + try { + Channel [] channels = new Channel[nbChannels]; + for(int i = 0; i < nbConnections; i++) { + connections[i] = connectionFactory.newConnection(); + for(int j = 0; j < nbChannelsPerConnection; j++) { + Channel channel = connections[i].createChannel(); + channel.basicQos(1); + channels[i * nbChannelsPerConnection + j] = channel; + } + } + + // consume messages without ack + for(int i = 0; i < nbOfMessages; i++) { + sendMessage(channels[random.nextInt(nbChannels)]); + } + + + List> tasks = new ArrayList>(); + for(int i = 0; i < nbTasks; i++) { + Channel channelForConsuming = channels[random.nextInt(nbChannels)]; + tasks.add(random.nextInt(10)%2 == 0 ? + new BasicGetTask(channelForConsuming, true) : + new BasicConsumeTask(channelForConsuming, true)); + } + executorService.invokeAll(tasks); + + assertThat(metrics.getPublishedMessages().getCount()).isEqualTo(nbOfMessages); + waitAtMost(timeout(), () -> metrics.getConsumedMessages().getCount() == nbOfMessages); + assertThat(metrics.getAcknowledgedMessages().getCount()).isEqualTo(0L); + + // to remove the listeners + for(int i = 0; i < nbChannels; i++) { + channels[i].close(); + Channel channel = connections[random.nextInt(nbConnections)].createChannel(); + channel.basicQos(1); + channels[i] = channel; + } + + // consume messages with ack + for(int i = 0; i < nbOfMessages; i++) { + sendMessage(channels[random.nextInt(nbChannels)]); + } + + executorService.shutdownNow(); + + executorService = Executors.newFixedThreadPool(nbTasks); + tasks = new ArrayList>(); + for(int i = 0; i < nbTasks; i++) { + Channel channelForConsuming = channels[i]; + tasks.add(random.nextBoolean() ? + new BasicGetTask(channelForConsuming, false) : + new BasicConsumeTask(channelForConsuming, false)); + } + executorService.invokeAll(tasks); + + assertThat(metrics.getPublishedMessages().getCount()).isEqualTo(2*nbOfMessages); + waitAtMost(timeout(), () -> metrics.getConsumedMessages().getCount() == 2*nbOfMessages); + waitAtMost(timeout(), () -> metrics.getAcknowledgedMessages().getCount() == nbOfMessages); + + // to remove the listeners + for(int i = 0; i < nbChannels; i++) { + channels[i].close(); + Channel channel = connections[random.nextInt(nbConnections)].createChannel(); + channel.basicQos(1); + channels[i] = channel; + } + + // consume messages and reject them + for(int i = 0; i < nbOfMessages; i++) { + sendMessage(channels[random.nextInt(nbChannels)]); + } + + executorService.shutdownNow(); + + executorService = Executors.newFixedThreadPool(nbTasks); + tasks = new ArrayList>(); + for(int i = 0; i < nbTasks; i++) { + Channel channelForConsuming = channels[i]; + tasks.add(random.nextBoolean() ? + new BasicGetRejectTask(channelForConsuming) : + new BasicConsumeRejectTask(channelForConsuming)); + } + executorService.invokeAll(tasks); + + assertThat(metrics.getPublishedMessages().getCount()).isEqualTo(3*nbOfMessages); + waitAtMost(timeout(), () -> metrics.getConsumedMessages().getCount() == 3*nbOfMessages); + waitAtMost(timeout(), () -> metrics.getAcknowledgedMessages().getCount() == nbOfMessages); + waitAtMost(timeout(), () -> metrics.getRejectedMessages().getCount() == nbOfMessages); + } finally { + for (Connection connection : connections) { + safeClose(connection); + } + executorService.shutdownNow(); + } + } + + @ParameterizedTest + @MethodSource("data") + public void errorInChannel(ConnectionFactory connectionFactory) throws IOException, TimeoutException { + StandardMetricsCollector metrics = new StandardMetricsCollector(); + connectionFactory.setMetricsCollector(metrics); + + Connection connection = null; + try { + connection = connectionFactory.newConnection(); + Channel channel = connection.createChannel(); + + assertThat(metrics.getConnections().getCount()).isEqualTo(1L); + assertThat(metrics.getChannels().getCount()).isEqualTo(1L); + + channel.basicPublish("unlikelynameforanexchange", "", null, "msg".getBytes("UTF-8")); + + waitAtMost(timeout(), () -> metrics.getChannels().getCount() == 0L); + assertThat(metrics.getConnections().getCount()).isEqualTo(1L); + } finally { + safeClose(connection); + } + } + + @Test public void checkListenersWithAutoRecoveryConnection() throws Exception { + ConnectionFactory connectionFactory = createConnectionFactory(); + connectionFactory.setNetworkRecoveryInterval(2000); + connectionFactory.setAutomaticRecoveryEnabled(true); + StandardMetricsCollector metrics = new StandardMetricsCollector(); + connectionFactory.setMetricsCollector(metrics); + + Connection connection = null; + try { + connection = connectionFactory.newConnection(UUID.randomUUID().toString()); + + Collection shutdownHooks = getShutdownHooks(connection); + assertThat(shutdownHooks.size()).isEqualTo(0); + + connection.createChannel(); + + assertThat(metrics.getConnections().getCount()).isEqualTo(1L); + assertThat(metrics.getChannels().getCount()).isEqualTo(1L); + + closeAndWaitForRecovery((AutorecoveringConnection) connection); + + assertThat(metrics.getConnections().getCount()).isEqualTo(1L); + assertThat(metrics.getChannels().getCount()).isEqualTo(1L); + + assertThat(shutdownHooks.size()).isEqualTo(0); + } finally { + safeClose(connection); + } + } + + @Test public void checkAcksWithAutomaticRecovery() throws Exception { + ConnectionFactory connectionFactory = createConnectionFactory(); + connectionFactory.setNetworkRecoveryInterval(2000); + connectionFactory.setAutomaticRecoveryEnabled(true); + StandardMetricsCollector metrics = new StandardMetricsCollector(); + connectionFactory.setMetricsCollector(metrics); + + Connection connection = null; + try { + connection = connectionFactory.newConnection(UUID.randomUUID().toString()); + + Channel channel1 = connection.createChannel(); + AtomicInteger ackedMessages = new AtomicInteger(0); + + channel1.basicConsume(QUEUE, false, (consumerTag, message) -> { + try { + channel1.basicAck(message.getEnvelope().getDeliveryTag(), false); + ackedMessages.incrementAndGet(); + } catch (Exception e) { } + }, tag -> {}); + + Channel channel2 = connection.createChannel(); + channel2.confirmSelect(); + int nbMessages = 10; + for (int i = 0; i < nbMessages; i++) { + sendMessage(channel2); + } + channel2.waitForConfirms(1000); + + closeAndWaitForRecovery((AutorecoveringConnection) connection); + + for (int i = 0; i < nbMessages; i++) { + sendMessage(channel2); + } + + waitAtMost(timeout(), () -> ackedMessages.get() == nbMessages * 2); + + assertThat(metrics.getConsumedMessages().getCount()).isEqualTo((long) (nbMessages * 2)); + assertThat(metrics.getAcknowledgedMessages().getCount()).isEqualTo((long) (nbMessages * 2)); + + } finally { + safeClose(connection); + } + } + + private static ConnectionFactory createConnectionFactory() { + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + connectionFactory.setAutomaticRecoveryEnabled(false); + return connectionFactory; + } + + private static ConnectionFactory createAutoRecoveryConnectionFactory() { + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + connectionFactory.setAutomaticRecoveryEnabled(true); + return connectionFactory; + } + + private void closeAndWaitForRecovery(AutorecoveringConnection connection) throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + connection.addRecoveryListener(new RecoveryListener() { + public void handleRecovery(Recoverable recoverable) { + latch.countDown(); + } + + @Override + public void handleRecoveryStarted(Recoverable recoverable) { + // no-op + } + }); + Host.closeConnection(connection); + assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); + } + + private Collection getShutdownHooks(Connection connection) throws NoSuchFieldException, IllegalAccessException { + Field shutdownHooksField = connection.getClass().getDeclaredField("shutdownHooks"); + shutdownHooksField.setAccessible(true); + return (Collection) shutdownHooksField.get(connection); + } + + private static class BasicGetTask implements Callable { + + final Channel channel; + final boolean autoAck; + final Random random = new Random(); + + private BasicGetTask(Channel channel, boolean autoAck) { + this.channel = channel; + this.autoAck = autoAck; + } + + @Override + public Void call() throws Exception { + GetResponse getResponse = this.channel.basicGet(QUEUE, autoAck); + if(!autoAck) { + channel.basicAck(getResponse.getEnvelope().getDeliveryTag(), random.nextBoolean()); + } + return null; + } + } + + private static class BasicConsumeTask implements Callable { + + final Channel channel; + final boolean autoAck; + final Random random = new Random(); + + private BasicConsumeTask(Channel channel, boolean autoAck) { + this.channel = channel; + this.autoAck = autoAck; + } + + @Override + public Void call() throws Exception { + this.channel.basicConsume(QUEUE, autoAck, new DefaultConsumer(channel) { + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + if(!autoAck) { + getChannel().basicAck(envelope.getDeliveryTag(), random.nextBoolean()); + } + } + }); + return null; + } + } + + private static class BasicGetRejectTask implements Callable { + + final Channel channel; + final Random random = new Random(); + + private BasicGetRejectTask(Channel channel) { + this.channel = channel; + } + + @Override + public Void call() throws Exception { + GetResponse response = channel.basicGet(QUEUE, false); + if(response != null) { + if(random.nextBoolean()) { + channel.basicNack(response.getEnvelope().getDeliveryTag(), random.nextBoolean(), false); + } else { + channel.basicReject(response.getEnvelope().getDeliveryTag(), false); + } + } + return null; + } + } + + private static class BasicConsumeRejectTask implements Callable { + + final Channel channel; + final Random random = new Random(); + + private BasicConsumeRejectTask(Channel channel) { + this.channel = channel; + } + + @Override + public Void call() throws Exception { + this.channel.basicConsume(QUEUE, false, new DefaultConsumer(channel) { + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + if(random.nextBoolean()) { + channel.basicNack(envelope.getDeliveryTag(), random.nextBoolean(), false); + } else { + channel.basicReject(envelope.getDeliveryTag(), false); + } + } + }); + return null; + } + } + + private void safeClose(Connection connection) { + if(connection != null) { + try { + connection.abort(); + } catch (Exception e) { + // OK + } + } + } + + private void sendMessage(Channel channel) throws IOException { + channel.basicPublish("", QUEUE, null, "msg".getBytes("UTF-8")); + } + + private Duration timeout() { + return Duration.ofSeconds(10); + } + + private static class MultipleAckConsumer extends DefaultConsumer { + + final boolean multiple; + private AtomicInteger ackedCount = new AtomicInteger(0); + + public MultipleAckConsumer(Channel channel, boolean multiple) { + super(channel); + this.multiple = multiple; + } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + try { + Thread.sleep(new Random().nextInt(10)); + } catch (InterruptedException e) { + throw new RuntimeException("Error during randomized wait",e); + } + getChannel().basicAck(envelope.getDeliveryTag(), multiple); + ackedCount.incrementAndGet(); + } + + int ackedCount() { + return this.ackedCount.get(); + } + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/MicrometerObservationCollectorMetrics.java b/src/test/java/com/rabbitmq/client/test/functional/MicrometerObservationCollectorMetrics.java new file mode 100644 index 0000000000..17647c9214 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/MicrometerObservationCollectorMetrics.java @@ -0,0 +1,392 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.test.functional; + +import static com.rabbitmq.client.test.TestUtils.waitAtMost; +import static org.assertj.core.api.Assertions.assertThat; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.observation.ObservationCollector; +import com.rabbitmq.client.observation.micrometer.MicrometerObservationCollectorBuilder; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.client.test.TestUtils; +import io.micrometer.observation.NullObservation; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.exporter.FinishedSpan; +import io.micrometer.tracing.test.SampleTestRunner; +import io.micrometer.tracing.test.simple.SpanAssert; +import io.micrometer.tracing.test.simple.SpansAssert; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Nested; + +public class MicrometerObservationCollectorMetrics extends BrokerTestCase { + + static final String QUEUE = "metrics.queue"; + private static final byte[] PAYLOAD = "msg".getBytes(StandardCharsets.UTF_8); + + private static ConnectionFactory createConnectionFactory() { + return createConnectionFactory(null); + } + + private static ConnectionFactory createConnectionFactory( + ObservationRegistry observationRegistry) { + return createConnectionFactory(false, observationRegistry); + } + + private static ConnectionFactory createConnectionFactory( + boolean keepObservationStartedOnBasicGet, ObservationRegistry observationRegistry) { + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + connectionFactory.setAutomaticRecoveryEnabled(true); + if (observationRegistry != null) { + ObservationCollector collector = + new MicrometerObservationCollectorBuilder() + .keepObservationStartedOnBasicGet(keepObservationStartedOnBasicGet) + .registry(observationRegistry) + .build(); + connectionFactory.setObservationCollector(collector); + } + return connectionFactory; + } + + private static Consumer consumer(DeliverCallback callback) { + return new Consumer() { + @Override + public void handleConsumeOk(String consumerTag) {} + + @Override + public void handleCancelOk(String consumerTag) {} + + @Override + public void handleCancel(String consumerTag) {} + + @Override + public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) {} + + @Override + public void handleRecoverOk(String consumerTag) {} + + @Override + public void handleDelivery( + String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) + throws IOException { + callback.handle(consumerTag, new Delivery(envelope, properties, body)); + } + }; + } + + @Override + protected void createResources() throws IOException { + channel.queueDeclare(QUEUE, true, false, false, null); + } + + @Override + protected void releaseResources() throws IOException { + channel.queueDelete(QUEUE); + } + + private void safeClose(Connection connection) { + if (connection != null) { + try { + connection.abort(); + } catch (Exception e) { + // OK + } + } + } + + private void sendMessage(Channel channel) throws IOException { + channel.basicPublish("", QUEUE, null, PAYLOAD); + } + + private abstract static class IntegrationTest extends SampleTestRunner { + + @Override + public TracingSetup[] getTracingSetup() { + return new TracingSetup[] {TracingSetup.IN_MEMORY_BRAVE, TracingSetup.ZIPKIN_BRAVE}; + } + } + + @Nested + class PublishConsume extends IntegrationTest { + + @Override + public SampleTestRunnerConsumer yourCode() { + return (buildingBlocks, meterRegistry) -> { + ConnectionFactory connectionFactory = createConnectionFactory(getObservationRegistry()); + Connection publishConnection = null, consumeConnection = null; + try { + publishConnection = connectionFactory.newConnection(); + Channel channel = publishConnection.createChannel(); + + sendMessage(channel); + + CountDownLatch consumeLatch = new CountDownLatch(1); + Consumer consumer = consumer((consumerTag, message) -> consumeLatch.countDown()); + + consumeConnection = connectionFactory.newConnection(); + channel = consumeConnection.createChannel(); + channel.basicConsume(QUEUE, true, consumer); + + assertThat(consumeLatch.await(10, TimeUnit.SECONDS)).isTrue(); + waitAtMost(() -> buildingBlocks.getFinishedSpans().size() == 2); + SpansAssert.assertThat(buildingBlocks.getFinishedSpans()).haveSameTraceId().hasSize(2); + SpanAssert.assertThat(buildingBlocks.getFinishedSpans().get(0)) + .hasNameEqualTo("metrics.queue publish") + .hasTag("messaging.rabbitmq.destination.routing_key", "metrics.queue") + .hasTag("messaging.destination.name", "amq.default") + .hasTag("messaging.message.payload_size_bytes", String.valueOf(PAYLOAD.length)) + .hasTagWithKey("net.sock.peer.addr") + .hasTag("net.sock.peer.port", "5672") + .hasTag("net.protocol.name", "amqp") + .hasTag("net.protocol.version", "0.9.1"); + SpanAssert.assertThat(buildingBlocks.getFinishedSpans().get(1)) + .hasNameEqualTo("metrics.queue process") + .hasTag("messaging.rabbitmq.destination.routing_key", "metrics.queue") + .hasTag("messaging.destination.name", "amq.default") + .hasTag("messaging.source.name", "metrics.queue") + .hasTag("messaging.message.payload_size_bytes", String.valueOf(PAYLOAD.length)) + .hasTag("net.protocol.name", "amqp") + .hasTag("net.protocol.version", "0.9.1"); + waitAtMost( + () -> + getMeterRegistry().find("rabbitmq.publish").timer() != null + && getMeterRegistry().find("rabbitmq.process").timer() != null); + getMeterRegistry() + .get("rabbitmq.publish") + .tag("messaging.operation", "publish") + .tag("messaging.system", "rabbitmq") + .timer(); + getMeterRegistry() + .get("rabbitmq.process") + .tag("messaging.operation", "process") + .tag("messaging.system", "rabbitmq") + .timer(); + } finally { + safeClose(publishConnection); + safeClose(consumeConnection); + } + }; + } + } + + @Nested + class PublishBasicGet extends IntegrationTest { + + @Override + public SampleTestRunnerConsumer yourCode() { + return (buildingBlocks, meterRegistry) -> { + ObservationRegistry observationRegistry = getObservationRegistry(); + ConnectionFactory connectionFactory = createConnectionFactory(observationRegistry); + Connection publishConnection = null, consumeConnection = null; + try { + publishConnection = connectionFactory.newConnection(); + Channel channel = publishConnection.createChannel(); + + new NullObservation(observationRegistry).observeChecked(() -> sendMessage(channel)); + + consumeConnection = connectionFactory.newConnection(); + Channel basicGetChannel = consumeConnection.createChannel(); + waitAtMost(() -> basicGetChannel.basicGet(QUEUE, true) != null); + waitAtMost(() -> buildingBlocks.getFinishedSpans().size() >= 3); + + Map> finishedSpans = + buildingBlocks.getFinishedSpans().stream() + .collect(Collectors.groupingBy(FinishedSpan::getTraceId)); + BDDAssertions.then(finishedSpans) + .as("One trace id for sending, one for polling") + .hasSize(2); + Collection> spans = finishedSpans.values(); + List sendAndReceiveSpans = + spans.stream() + .filter(f -> f.size() == 2) + .findFirst() + .orElseThrow( + () -> + new AssertionError( + "null_observation (fake nulling observation) -> produce -> consume")); + sendAndReceiveSpans.sort(Comparator.comparing(FinishedSpan::getStartTimestamp)); + SpanAssert.assertThat(sendAndReceiveSpans.get(0)) + .hasNameEqualTo("metrics.queue publish") + .hasTag("messaging.rabbitmq.destination.routing_key", "metrics.queue") + .hasTag("messaging.destination.name", "amq.default") + .hasTag("messaging.message.payload_size_bytes", String.valueOf(PAYLOAD.length)) + .hasTagWithKey("net.sock.peer.addr") + .hasTag("net.sock.peer.port", "5672") + .hasTag("net.protocol.name", "amqp") + .hasTag("net.protocol.version", "0.9.1"); + SpanAssert.assertThat(sendAndReceiveSpans.get(1)) + .hasNameEqualTo("metrics.queue receive") + .hasTag("messaging.rabbitmq.destination.routing_key", "metrics.queue") + .hasTag("messaging.destination.name", "amq.default") + .hasTag("messaging.source.name", "metrics.queue") + .hasTag("messaging.message.payload_size_bytes", String.valueOf(PAYLOAD.length)) + .hasTag("net.protocol.name", "amqp") + .hasTag("net.protocol.version", "0.9.1"); + List pollingSpans = + spans.stream() + .filter(f -> f.size() == 1) + .findFirst() + .orElseThrow(() -> new AssertionError("rabbitmq.receive (child of test span)")); + SpanAssert.assertThat(pollingSpans.get(0)).hasNameEqualTo("rabbitmq.receive"); + waitAtMost( + () -> + getMeterRegistry().find("rabbitmq.publish").timer() != null + && getMeterRegistry().find("rabbitmq.receive").timer() != null); + getMeterRegistry() + .get("rabbitmq.publish") + .tag("messaging.operation", "publish") + .tag("messaging.system", "rabbitmq") + .timer(); + getMeterRegistry() + .get("rabbitmq.receive") + .tag("messaging.operation", "receive") + .tag("messaging.system", "rabbitmq") + .timer(); + } finally { + safeClose(publishConnection); + safeClose(consumeConnection); + } + }; + } + } + + @Nested + class PublishBasicGetKeepObservationOpen extends IntegrationTest { + + @Override + public SampleTestRunnerConsumer yourCode() { + return (buildingBlocks, meterRegistry) -> { + ObservationRegistry observationRegistry = getObservationRegistry(); + ConnectionFactory connectionFactory = createConnectionFactory(true, observationRegistry); + Connection publishConnection = null, consumeConnection = null; + try { + publishConnection = connectionFactory.newConnection(); + Channel channel = publishConnection.createChannel(); + + new NullObservation(observationRegistry).observeChecked(() -> sendMessage(channel)); + + consumeConnection = connectionFactory.newConnection(); + Channel basicGetChannel = consumeConnection.createChannel(); + waitAtMost(() -> basicGetChannel.basicGet(QUEUE, true) != null); + Observation.Scope scope = observationRegistry.getCurrentObservationScope(); + assertThat(scope).isNotNull(); + // creating a dummy span to make sure it's wrapped into the receive one + buildingBlocks.getTracer().nextSpan().name("foobar").start().end(); + scope.close(); + scope.getCurrentObservation().stop(); + waitAtMost(() -> buildingBlocks.getFinishedSpans().size() >= 3 + 1); + Map> finishedSpans = + buildingBlocks.getFinishedSpans().stream() + .collect(Collectors.groupingBy(FinishedSpan::getTraceId)); + BDDAssertions.then(finishedSpans) + .as("One trace id for sending, one for polling") + .hasSize(2); + Collection> spans = finishedSpans.values(); + List sendAndReceiveSpans = + spans.stream() + .filter(f -> f.size() == 2 + 1) + .findFirst() + .orElseThrow( + () -> + new AssertionError( + "null_observation (fake nulling observation) -> produce -> consume")); + sendAndReceiveSpans.sort(Comparator.comparing(FinishedSpan::getStartTimestamp)); + SpanAssert.assertThat(sendAndReceiveSpans.get(0)) + .hasNameEqualTo("metrics.queue publish") + .hasTag("messaging.rabbitmq.destination.routing_key", "metrics.queue") + .hasTag("messaging.destination.name", "amq.default") + .hasTag("messaging.message.payload_size_bytes", String.valueOf(PAYLOAD.length)) + .hasTagWithKey("net.sock.peer.addr") + .hasTag("net.sock.peer.port", "5672") + .hasTag("net.protocol.name", "amqp") + .hasTag("net.protocol.version", "0.9.1"); + SpanAssert.assertThat(sendAndReceiveSpans.get(1)) + .hasNameEqualTo("metrics.queue receive") + .hasTag("messaging.rabbitmq.destination.routing_key", "metrics.queue") + .hasTag("messaging.destination.name", "amq.default") + .hasTag("messaging.source.name", "metrics.queue") + .hasTag("messaging.message.payload_size_bytes", String.valueOf(PAYLOAD.length)) + .hasTag("net.protocol.name", "amqp") + .hasTag("net.protocol.version", "0.9.1"); + List pollingSpans = + spans.stream() + .filter(f -> f.size() == 1) + .findFirst() + .orElseThrow(() -> new AssertionError("rabbitmq.receive (child of test span)")); + SpanAssert.assertThat(pollingSpans.get(0)).hasNameEqualTo("rabbitmq.receive"); + waitAtMost( + () -> + getMeterRegistry().find("rabbitmq.publish").timer() != null + && getMeterRegistry().find("rabbitmq.receive").timer() != null); + getMeterRegistry() + .get("rabbitmq.publish") + .tag("messaging.operation", "publish") + .tag("messaging.system", "rabbitmq") + .timer(); + getMeterRegistry() + .get("rabbitmq.receive") + .tag("messaging.operation", "receive") + .tag("messaging.system", "rabbitmq") + .timer(); + } finally { + safeClose(publishConnection); + safeClose(consumeConnection); + } + }; + } + } + + @Nested + class ConsumeWithoutObservationShouldNotFail extends IntegrationTest { + + @Override + public SampleTestRunnerConsumer yourCode() { + return (buildingBlocks, meterRegistry) -> { + ConnectionFactory publishCf = createConnectionFactory(); + ConnectionFactory consumeCf = createConnectionFactory(getObservationRegistry()); + Connection publishConnection = null, consumeConnection = null; + try { + publishConnection = publishCf.newConnection(); + Channel channel = publishConnection.createChannel(); + + sendMessage(channel); + + CountDownLatch consumeLatch = new CountDownLatch(1); + Consumer consumer = consumer((consumerTag, message) -> consumeLatch.countDown()); + + consumeConnection = consumeCf.newConnection(); + channel = consumeConnection.createChannel(); + channel.basicConsume(QUEUE, true, consumer); + + assertThat(consumeLatch.await(10, TimeUnit.SECONDS)).isTrue(); + } finally { + safeClose(publishConnection); + safeClose(consumeConnection); + } + }; + } + } +} diff --git a/test/src/com/rabbitmq/client/test/functional/Nack.java b/src/test/java/com/rabbitmq/client/test/functional/Nack.java similarity index 56% rename from test/src/com/rabbitmq/client/test/functional/Nack.java rename to src/test/java/com/rabbitmq/client/test/functional/Nack.java index 4cc01ff5a8..50c8ebe12a 100644 --- a/test/src/com/rabbitmq/client/test/functional/Nack.java +++ b/src/test/java/com/rabbitmq/client/test/functional/Nack.java @@ -1,40 +1,69 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.test.TestUtils; +import com.rabbitmq.client.test.TestUtils.CallableFunction; +import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.UUID; + import com.rabbitmq.client.AMQP; import com.rabbitmq.client.QueueingConsumer; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class Nack extends AbstractRejectTest { - public void testSingleNack() throws Exception { - String q = - channel.queueDeclare("", false, true, false, null).getQueue(); + public static Object[] queueCreators() { + return new Object[] { + (CallableFunction) channel -> { + String q = UUID.randomUUID().toString(); + channel.queueDeclare(q, true, false, false, Collections.singletonMap("x-queue-type", "quorum")); + return q; + }, + (CallableFunction) channel -> { + String q = UUID.randomUUID().toString(); + channel.queueDeclare(q, true, false, false, Collections.singletonMap("x-queue-type", "classic")); + return q; + }}; + } + + @ParameterizedTest + @MethodSource("queueCreators") + public void singleNack(TestUtils.CallableFunction queueCreator) throws Exception { + String q = queueCreator.apply(channel); byte[] m1 = "1".getBytes(); byte[] m2 = "2".getBytes(); + channel.confirmSelect(); + basicPublishVolatile(m1, q); basicPublishVolatile(m2, q); + channel.waitForConfirmsOrDie(1000); + long tag1 = checkDelivery(channel.basicGet(q, false), m1, false); long tag2 = checkDelivery(channel.basicGet(q, false), m2, false); @@ -57,20 +86,25 @@ public void testSingleNack() throws Exception { expectError(AMQP.PRECONDITION_FAILED); } - public void testMultiNack() throws Exception { - String q = - channel.queueDeclare("", false, true, false, null).getQueue(); + @ParameterizedTest + @MethodSource("queueCreators") + public void multiNack(TestUtils.CallableFunction queueCreator) throws Exception { + String q = queueCreator.apply(channel); byte[] m1 = "1".getBytes(); byte[] m2 = "2".getBytes(); byte[] m3 = "3".getBytes(); byte[] m4 = "4".getBytes(); + channel.confirmSelect(); + basicPublishVolatile(m1, q); basicPublishVolatile(m2, q); basicPublishVolatile(m3, q); basicPublishVolatile(m4, q); + channel.waitForConfirmsOrDie(1000); + checkDelivery(channel.basicGet(q, false), m1, false); long tag1 = checkDelivery(channel.basicGet(q, false), m2, false); checkDelivery(channel.basicGet(q, false), m3, false); @@ -99,16 +133,21 @@ public void testMultiNack() throws Exception { expectError(AMQP.PRECONDITION_FAILED); } - public void testNackAll() throws Exception { - String q = - channel.queueDeclare("", false, true, false, null).getQueue(); + @ParameterizedTest + @MethodSource("queueCreators") + public void nackAll(TestUtils.CallableFunction queueCreator) throws Exception { + String q = queueCreator.apply(channel); byte[] m1 = "1".getBytes(); byte[] m2 = "2".getBytes(); + channel.confirmSelect(); + basicPublishVolatile(m1, q); basicPublishVolatile(m2, q); + channel.waitForConfirmsOrDie(1000); + checkDelivery(channel.basicGet(q, false), m1, false); checkDelivery(channel.basicGet(q, false), m2, false); @@ -135,7 +174,7 @@ private long checkDeliveries(QueueingConsumer c, byte[]... messages) for(int x = 0; x < messages.length; x++) { QueueingConsumer.Delivery delivery = c.nextDelivery(); String m = new String(delivery.getBody()); - assertTrue("Unexpected message", msgSet.remove(m)); + assertTrue(msgSet.remove(m), "Unexpected message"); checkDelivery(delivery, m.getBytes(), true); lastTag = delivery.getEnvelope().getDeliveryTag(); } diff --git a/src/test/java/com/rabbitmq/client/test/functional/NoRequeueOnCancel.java b/src/test/java/com/rabbitmq/client/test/functional/NoRequeueOnCancel.java new file mode 100644 index 0000000000..9e69f38cbe --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/NoRequeueOnCancel.java @@ -0,0 +1,68 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.rabbitmq.client.*; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.test.BrokerTestCase; + +public class NoRequeueOnCancel extends BrokerTestCase +{ + protected final String Q = "NoRequeueOnCancel"; + + protected void createResources() throws IOException { + channel.queueDeclare(Q, false, false, false, null); + } + + protected void releaseResources() throws IOException { + channel.queueDelete(Q); + } + + @Test public void noRequeueOnCancel() + throws IOException, InterruptedException + { + channel.basicPublish("", Q, null, "1".getBytes()); + + final CountDownLatch latch = new CountDownLatch(1); + Consumer c = new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + latch.countDown(); + } + }; + String consumerTag = channel.basicConsume(Q, false, c); + assertTrue(latch.await(5, TimeUnit.SECONDS)); + + channel.basicCancel(consumerTag); + + assertNull(channel.basicGet(Q, true)); + + closeChannel(); + openChannel(); + + assertNotNull(channel.basicGet(Q, true)); + } +} diff --git a/test/src/com/rabbitmq/client/test/functional/Nowait.java b/src/test/java/com/rabbitmq/client/test/functional/Nowait.java similarity index 73% rename from test/src/com/rabbitmq/client/test/functional/Nowait.java rename to src/test/java/com/rabbitmq/client/test/functional/Nowait.java index db0faca82a..ea82147753 100644 --- a/test/src/com/rabbitmq/client/test/functional/Nowait.java +++ b/src/test/java/com/rabbitmq/client/test/functional/Nowait.java @@ -1,3 +1,18 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.test.functional; import com.rabbitmq.client.test.BrokerTestCase; diff --git a/test/src/com/rabbitmq/client/test/functional/PerConsumerPrefetch.java b/src/test/java/com/rabbitmq/client/test/functional/PerConsumerPrefetch.java similarity index 74% rename from test/src/com/rabbitmq/client/test/functional/PerConsumerPrefetch.java rename to src/test/java/com/rabbitmq/client/test/functional/PerConsumerPrefetch.java index 0b5531da1d..8b465c5544 100644 --- a/test/src/com/rabbitmq/client/test/functional/PerConsumerPrefetch.java +++ b/src/test/java/com/rabbitmq/client/test/functional/PerConsumerPrefetch.java @@ -1,15 +1,32 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.QueueingConsumer; -import com.rabbitmq.client.QueueingConsumer.Delivery; -import com.rabbitmq.client.test.BrokerTestCase; +import static com.rabbitmq.client.test.functional.QosTests.drain; import java.io.IOException; import java.util.Arrays; import java.util.List; -import static com.rabbitmq.client.test.functional.QosTests.drain; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.QueueingConsumer; +import com.rabbitmq.client.QueueingConsumer.Delivery; +import com.rabbitmq.client.test.BrokerTestCase; public class PerConsumerPrefetch extends BrokerTestCase { private String q; @@ -23,7 +40,7 @@ private interface Closure { public void makeMore(List deliveries) throws IOException; } - public void testSingleAck() throws IOException { + @Test public void singleAck() throws IOException { testPrefetch(new Closure() { public void makeMore(List deliveries) throws IOException { for (Delivery del : deliveries) { @@ -33,7 +50,7 @@ public void makeMore(List deliveries) throws IOException { }); } - public void testMultiAck() throws IOException { + @Test public void multiAck() throws IOException { testPrefetch(new Closure() { public void makeMore(List deliveries) throws IOException { ack(deliveries.get(deliveries.size() - 1), true); @@ -41,7 +58,7 @@ public void makeMore(List deliveries) throws IOException { }); } - public void testSingleNack() throws IOException { + @Test public void singleNack() throws IOException { for (final boolean requeue: Arrays.asList(false, true)) { testPrefetch(new Closure() { public void makeMore(List deliveries) throws IOException { @@ -53,7 +70,7 @@ public void makeMore(List deliveries) throws IOException { } } - public void testMultiNack() throws IOException { + @Test public void multiNack() throws IOException { for (final boolean requeue: Arrays.asList(false, true)) { testPrefetch(new Closure() { public void makeMore(List deliveries) throws IOException { @@ -63,7 +80,7 @@ public void makeMore(List deliveries) throws IOException { } } - public void testRecover() throws IOException { + @Test public void recover() throws IOException { testPrefetch(new Closure() { public void makeMore(List deliveries) throws IOException { channel.basicRecover(); @@ -84,7 +101,7 @@ private void testPrefetch(Closure closure) throws IOException { drain(c, 5); } - public void testPrefetchOnEmpty() throws IOException { + @Test public void prefetchOnEmpty() throws IOException { QueueingConsumer c = new QueueingConsumer(channel); publish(q, 5); consume(c, 10, false); @@ -93,14 +110,14 @@ public void testPrefetchOnEmpty() throws IOException { drain(c, 5); } - public void testAutoAckIgnoresPrefetch() throws IOException { + @Test public void autoAckIgnoresPrefetch() throws IOException { QueueingConsumer c = new QueueingConsumer(channel); publish(q, 10); consume(c, 1, true); drain(c, 10); } - public void testPrefetchZeroMeansInfinity() throws IOException { + @Test public void prefetchZeroMeansInfinity() throws IOException { QueueingConsumer c = new QueueingConsumer(channel); publish(q, 10); consume(c, 0, false); diff --git a/test/src/com/rabbitmq/client/test/functional/PerMessageTTL.java b/src/test/java/com/rabbitmq/client/test/functional/PerMessageTTL.java similarity index 55% rename from test/src/com/rabbitmq/client/test/functional/PerMessageTTL.java rename to src/test/java/com/rabbitmq/client/test/functional/PerMessageTTL.java index df1f812582..2a002b6ec4 100644 --- a/test/src/com/rabbitmq/client/test/functional/PerMessageTTL.java +++ b/src/test/java/com/rabbitmq/client/test/functional/PerMessageTTL.java @@ -1,28 +1,32 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.QueueingConsumer; -import com.rabbitmq.client.MessageProperties; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import java.io.IOException; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.MessageProperties; +import com.rabbitmq.client.QueueingConsumer; + public class PerMessageTTL extends TTLHandling { protected Object sessionTTL; @@ -42,7 +46,7 @@ protected AMQP.Queue.DeclareOk declareQueue(String name, Object ttlValue) throws return this.channel.queueDeclare(name, false, true, false, null); } - public void testExpiryWhenConsumerIsLateToTheParty() throws Exception { + @Test public void expiryWhenConsumerIsLateToTheParty() throws Exception { declareAndBindQueue(500); publish(MSG[0]); @@ -54,11 +58,11 @@ public void testExpiryWhenConsumerIsLateToTheParty() throws Exception { QueueingConsumer c = new QueueingConsumer(channel); channel.basicConsume(TTL_QUEUE_NAME, c); - assertNotNull("Message unexpectedly expired", c.nextDelivery(100)); - assertNull("Message should have been expired!!", c.nextDelivery(100)); + assertNotNull(c.nextDelivery(100), "Message unexpectedly expired"); + assertNull(c.nextDelivery(100), "Message should have been expired!!"); } - public void testRestartingExpiry() throws Exception { + @Test public void restartingExpiry() throws Exception { final String expiryDelay = "2000"; declareDurableQueue(TTL_QUEUE_NAME); bindQueue(); @@ -67,11 +71,10 @@ public void testRestartingExpiry() throws Exception { .builder() .expiration(expiryDelay) .build(), new byte[]{}); - long expiryStartTime = System.currentTimeMillis(); restart(); Thread.sleep(Integer.parseInt(expiryDelay)); try { - assertNull("Message should have expired after broker restart", get()); + assertNull(get(), "Message should have expired after broker restart"); } finally { deleteQueue(TTL_QUEUE_NAME); } diff --git a/test/src/com/rabbitmq/client/test/functional/PerQueueTTL.java b/src/test/java/com/rabbitmq/client/test/functional/PerQueueTTL.java similarity index 59% rename from test/src/com/rabbitmq/client/test/functional/PerQueueTTL.java rename to src/test/java/com/rabbitmq/client/test/functional/PerQueueTTL.java index e6cd19cb47..993ec83923 100644 --- a/test/src/com/rabbitmq/client/test/functional/PerQueueTTL.java +++ b/src/test/java/com/rabbitmq/client/test/functional/PerQueueTTL.java @@ -1,29 +1,32 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.MessageProperties; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.util.Collections; import java.util.Map; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.MessageProperties; + public class PerQueueTTL extends TTLHandling { protected static final String TTL_ARG = "x-message-ttl"; @@ -34,7 +37,7 @@ protected AMQP.Queue.DeclareOk declareQueue(String name, Object ttlValue) throws return this.channel.queueDeclare(name, false, true, false, argMap); } - public void testQueueReDeclareEquivalence() throws Exception { + @Test public void queueReDeclareEquivalence() throws Exception { declareQueue(10); try { declareQueue(20); @@ -44,14 +47,14 @@ public void testQueueReDeclareEquivalence() throws Exception { } } - public void testQueueReDeclareSemanticEquivalence() throws Exception { + @Test public void queueReDeclareSemanticEquivalence() throws Exception { declareQueue((byte)10); declareQueue(10); declareQueue((short)10); declareQueue(10L); } - public void testQueueReDeclareSemanticNonEquivalence() throws Exception { + @Test public void queueReDeclareSemanticNonEquivalence() throws Exception { declareQueue(10); try { declareQueue(10.0); diff --git a/test/src/com/rabbitmq/client/test/functional/PerQueueVsPerMessageTTL.java b/src/test/java/com/rabbitmq/client/test/functional/PerQueueVsPerMessageTTL.java similarity index 55% rename from test/src/com/rabbitmq/client/test/functional/PerQueueVsPerMessageTTL.java rename to src/test/java/com/rabbitmq/client/test/functional/PerQueueVsPerMessageTTL.java index a7c1502059..0181ebce63 100644 --- a/test/src/com/rabbitmq/client/test/functional/PerQueueVsPerMessageTTL.java +++ b/src/test/java/com/rabbitmq/client/test/functional/PerQueueVsPerMessageTTL.java @@ -1,14 +1,33 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.AMQP; +import static org.junit.jupiter.api.Assertions.assertNull; import java.io.IOException; import java.util.Collections; import java.util.Map; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; + public class PerQueueVsPerMessageTTL extends PerMessageTTL { - public void testSmallerPerQueueExpiryWins() throws IOException, InterruptedException { + @Test public void smallerPerQueueExpiryWins() throws IOException, InterruptedException { declareAndBindQueue(10); this.sessionTTL = 1000; @@ -16,7 +35,7 @@ public void testSmallerPerQueueExpiryWins() throws IOException, InterruptedExcep Thread.sleep(100); - assertNull("per-queue ttl should have removed message after 10ms!", get()); + assertNull(get(), "per-queue ttl should have removed message after 10ms"); } @Override diff --git a/test/src/com/rabbitmq/client/test/functional/Policies.java b/src/test/java/com/rabbitmq/client/test/functional/Policies.java similarity index 79% rename from test/src/com/rabbitmq/client/test/functional/Policies.java rename to src/test/java/com/rabbitmq/client/test/functional/Policies.java index f3c8e1fc83..6448e810fe 100644 --- a/test/src/com/rabbitmq/client/test/functional/Policies.java +++ b/src/test/java/com/rabbitmq/client/test/functional/Policies.java @@ -1,26 +1,23 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.client.test.server.HATests; -import com.rabbitmq.tools.Host; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.HashMap; @@ -28,6 +25,13 @@ import java.util.Map; import java.util.Set; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.tools.Host; + public class Policies extends BrokerTestCase { private static final int DELAY = 100; // MILLIS @@ -43,7 +47,7 @@ public class Policies extends BrokerTestCase { channel.exchangeDeclare("has-ae-args", "fanout", false, false, args); } - public void testAlternateExchange() throws IOException, InterruptedException { + @Test public void alternateExchange() throws IOException, InterruptedException { String q = declareQueue(); channel.exchangeDeclare("ae", "fanout", false, true, null); channel.queueBind(q, "ae", ""); @@ -56,7 +60,7 @@ public void testAlternateExchange() throws IOException, InterruptedException { } // i.e. the argument takes priority over the policy - public void testAlternateExchangeArgs() throws IOException { + @Test public void alternateExchangeArgs() throws IOException { String q = declareQueue(); channel.exchangeDeclare("ae2", "fanout", false, true, null); channel.queueBind(q, "ae2", ""); @@ -64,7 +68,7 @@ public void testAlternateExchangeArgs() throws IOException { assertDelivered(q, 1); } - public void testDeadLetterExchange() throws IOException, InterruptedException { + @Test public void deadLetterExchange() throws IOException, InterruptedException { Map args = ttlArgs(0); String src = declareQueue("has-dlx", args); String dest = declareQueue(); @@ -82,7 +86,7 @@ public void testDeadLetterExchange() throws IOException, InterruptedException { } // again the argument takes priority over the policy - public void testDeadLetterExchangeArgs() throws IOException, InterruptedException { + @Test public void deadLetterExchangeArgs() throws IOException, InterruptedException { Map args = ttlArgs(0); args.put("x-dead-letter-exchange", "dlx2"); args.put("x-dead-letter-routing-key", "rk2"); @@ -96,7 +100,7 @@ public void testDeadLetterExchangeArgs() throws IOException, InterruptedExceptio assertEquals("rk2", resp.getEnvelope().getRoutingKey()); } - public void testTTL() throws IOException, InterruptedException { + @Test public void tTL() throws IOException, InterruptedException { String q = declareQueue("has-ttl", null); basicPublishVolatile(q); Thread.sleep(2 * DELAY); @@ -109,7 +113,7 @@ public void testTTL() throws IOException, InterruptedException { } // Test that we get lower of args and policy - public void testTTLArgs() throws IOException, InterruptedException { + @Test public void tTLArgs() throws IOException, InterruptedException { String q = declareQueue("has-ttl", ttlArgs(3 * DELAY)); basicPublishVolatile(q); Thread.sleep(2 * DELAY); @@ -124,7 +128,7 @@ public void testTTLArgs() throws IOException, InterruptedException { assertDelivered(q, 0); } - public void testExpires() throws IOException, InterruptedException { + @Test public void expires() throws IOException, InterruptedException { String q = declareQueue("has-expires", null); Thread.sleep(2 * DELAY); assertFalse(queueExists(q)); @@ -136,7 +140,7 @@ public void testExpires() throws IOException, InterruptedException { } // Test that we get lower of args and policy - public void testExpiresArgs() throws IOException, InterruptedException { + @Test public void expiresArgs() throws IOException, InterruptedException { String q = declareQueue("has-expires", args("x-expires", 3 * DELAY)); Thread.sleep(2 * DELAY); assertFalse(queueExists(q)); @@ -147,7 +151,7 @@ public void testExpiresArgs() throws IOException, InterruptedException { assertTrue(queueExists(q)); } - public void testMaxLength() throws IOException, InterruptedException { + @Test public void maxLength() throws IOException, InterruptedException { String q = declareQueue("has-max-length", null); basicPublishVolatileN(q, 3); assertDelivered(q, 1); @@ -157,7 +161,7 @@ public void testMaxLength() throws IOException, InterruptedException { assertDelivered(q, 3); } - public void testMaxLengthArgs() throws IOException, InterruptedException { + @Test public void maxLengthArgs() throws IOException, InterruptedException { String q = declareQueue("has-max-length", args("x-max-length", 2)); basicPublishVolatileN(q, 3); assertDelivered(q, 1); @@ -176,12 +180,6 @@ public void testMaxLengthArgs() throws IOException, InterruptedException { private final Set policies = new HashSet(); private void setPolicy(String name, String pattern, String definition) throws IOException { - // We need to override the HA policy that we use in HATests, so - // priority 1. But we still want a valid test of HA, so add the - // ha-mode definition. - if (HATests.HA_TESTS_RUNNING) { - definition += ",\"ha-mode\":\"all\""; - } Host.rabbitmqctl("set_policy --priority 1 " + name + " " + pattern + " {" + escapeDefinition(definition) + "}"); policies.add(name); diff --git a/test/src/com/rabbitmq/client/test/functional/QosTests.java b/src/test/java/com/rabbitmq/client/test/functional/QosTests.java similarity index 87% rename from test/src/com/rabbitmq/client/test/functional/QosTests.java rename to src/test/java/com/rabbitmq/client/test/functional/QosTests.java index 7dfd1ca13b..18e0039c02 100644 --- a/test/src/com/rabbitmq/client/test/functional/QosTests.java +++ b/src/test/java/com/rabbitmq/client/test/functional/QosTests.java @@ -1,23 +1,26 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.test.BrokerTestCase; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -26,6 +29,9 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; @@ -33,18 +39,18 @@ import com.rabbitmq.client.GetResponse; import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.QueueingConsumer.Delivery; +import com.rabbitmq.client.test.BrokerTestCase; public class QosTests extends BrokerTestCase { - protected void setUp() - throws IOException - { + public void setUp() + throws IOException, TimeoutException { openConnection(); openChannel(); } - protected void tearDown() + public void tearDown() throws IOException { closeChannel(); @@ -84,7 +90,7 @@ public static List drain(QueueingConsumer c, int n) return res; } - public void testMessageLimitPrefetchSizeFails() + @Test public void messageLimitPrefetchSizeFails() throws IOException { try { @@ -95,7 +101,7 @@ public void testMessageLimitPrefetchSizeFails() } } - public void testMessageLimitUnlimited() + @Test public void messageLimitUnlimited() throws IOException { QueueingConsumer c = new QueueingConsumer(channel); @@ -103,7 +109,7 @@ public void testMessageLimitUnlimited() drain(c, 2); } - public void testNoAckNoAlterLimit() + @Test public void noAckNoAlterLimit() throws IOException { QueueingConsumer c = new QueueingConsumer(channel); @@ -113,7 +119,7 @@ public void testNoAckNoAlterLimit() drain(c, 2); } - public void testNoAckObeysLimit() + @Test public void noAckObeysLimit() throws IOException { channel.basicQos(1, true); @@ -136,7 +142,7 @@ public void testNoAckObeysLimit() drain(c2, 1); } - public void testPermutations() + @Test public void permutations() throws IOException { closeChannel(); @@ -153,7 +159,7 @@ public void testPermutations() } } - public void testFairness() + @Test public void fairness() throws IOException { QueueingConsumer c = new QueueingConsumer(channel); @@ -182,7 +188,7 @@ public void testFairness() } - public void testSingleChannelAndQueueFairness() + @Test public void singleChannelAndQueueFairness() throws IOException { //check that when we have multiple consumers on the same @@ -231,7 +237,7 @@ public void testSingleChannelAndQueueFairness() assertTrue(counts.get("c2").intValue() > 0); } - public void testConsumerLifecycle() + @Test public void consumerLifecycle() throws IOException { channel.basicQos(1, true); @@ -252,7 +258,7 @@ public void testConsumerLifecycle() channel.queueDelete(queue); } - public void testSetLimitAfterConsume() + @Test public void setLimitAfterConsume() throws IOException { QueueingConsumer c = new QueueingConsumer(channel); @@ -267,7 +273,7 @@ public void testSetLimitAfterConsume() drain(c, 1); } - public void testLimitIncrease() + @Test public void limitIncrease() throws IOException { QueueingConsumer c = new QueueingConsumer(channel); @@ -276,7 +282,7 @@ public void testLimitIncrease() drain(c, 1); } - public void testLimitDecrease() + @Test public void limitDecrease() throws IOException { QueueingConsumer c = new QueueingConsumer(channel); @@ -287,7 +293,7 @@ public void testLimitDecrease() drain(c, 1); } - public void testLimitedToUnlimited() + @Test public void limitedToUnlimited() throws IOException { QueueingConsumer c = new QueueingConsumer(channel); @@ -296,7 +302,7 @@ public void testLimitedToUnlimited() drain(c, 2); } - public void testLimitingMultipleChannels() + @Test public void limitingMultipleChannels() throws IOException { Channel ch1 = connection.createChannel(); @@ -316,11 +322,11 @@ public void testLimitingMultipleChannels() ackDelivery(ch2, d2.remove(0), true); drain(c1, 1); drain(c2, 1); - ch1.close(); - ch2.close(); + ch1.abort(); + ch2.abort(); } - public void testLimitInheritsUnackedCount() + @Test public void limitInheritsUnackedCount() throws IOException { QueueingConsumer c = new QueueingConsumer(channel); @@ -332,7 +338,7 @@ public void testLimitInheritsUnackedCount() drain(c, 1); } - public void testRecoverReducesLimit() throws Exception { + @Test public void recoverReducesLimit() throws Exception { channel.basicQos(2, true); QueueingConsumer c = new QueueingConsumer(channel); declareBindConsume(c); diff --git a/test/src/com/rabbitmq/client/test/functional/QueueExclusivity.java b/src/test/java/com/rabbitmq/client/test/functional/QueueExclusivity.java similarity index 71% rename from test/src/com/rabbitmq/client/test/functional/QueueExclusivity.java rename to src/test/java/com/rabbitmq/client/test/functional/QueueExclusivity.java index 63e5ef7edc..ae6609e9cd 100644 --- a/test/src/com/rabbitmq/client/test/functional/QueueExclusivity.java +++ b/src/test/java/com/rabbitmq/client/test/functional/QueueExclusivity.java @@ -1,24 +1,28 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.IOException; import java.util.HashMap; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; @@ -35,7 +39,7 @@ public class QueueExclusivity extends BrokerTestCase { public Channel altChannel; final String q = "exclusiveQ"; - protected void createResources() throws IOException { + protected void createResources() throws IOException, TimeoutException { altConnection = connectionFactory.newConnection(); altChannel = altConnection.createChannel(); altChannel.queueDeclare(q, @@ -49,7 +53,7 @@ protected void releaseResources() throws IOException { } } - public void testQueueExclusiveForPassiveDeclare() throws Exception { + @Test public void queueExclusiveForPassiveDeclare() throws Exception { try { channel.queueDeclarePassive(q); } catch (IOException ioe) { @@ -61,7 +65,7 @@ public void testQueueExclusiveForPassiveDeclare() throws Exception { // This is a different scenario because active declare takes notice of // the all the arguments - public void testQueueExclusiveForDeclare() throws Exception { + @Test public void queueExclusiveForDeclare() throws Exception { try { channel.queueDeclare(q, false, true, false, noArgs); } catch (IOException ioe) { @@ -71,7 +75,7 @@ public void testQueueExclusiveForDeclare() throws Exception { fail("Active queue declaration of an exclusive queue from another connection should fail"); } - public void testQueueExclusiveForConsume() throws Exception { + @Test public void queueExclusiveForConsume() throws Exception { QueueingConsumer c = new QueueingConsumer(channel); try { channel.basicConsume(q, c); @@ -82,7 +86,7 @@ public void testQueueExclusiveForConsume() throws Exception { fail("Exclusive queue should be locked for basic consume from another connection"); } - public void testQueueExclusiveForPurge() throws Exception { + @Test public void queueExclusiveForPurge() throws Exception { try { channel.queuePurge(q); } catch (IOException ioe) { @@ -92,7 +96,7 @@ public void testQueueExclusiveForPurge() throws Exception { fail("Exclusive queue should be locked for queue purge from another connection"); } - public void testQueueExclusiveForDelete() throws Exception { + @Test public void queueExclusiveForDelete() throws Exception { try { channel.queueDelete(q); } catch (IOException ioe) { @@ -102,7 +106,7 @@ public void testQueueExclusiveForDelete() throws Exception { fail("Exclusive queue should be locked for queue delete from another connection"); } - public void testQueueExclusiveForBind() throws Exception { + @Test public void queueExclusiveForBind() throws Exception { try { channel.queueBind(q, "amq.direct", ""); } catch (IOException ioe) { @@ -119,7 +123,7 @@ public void testQueueExclusiveForBind() throws Exception { // basic.cancel is inherently local to a channel, so it // *doesn't* make sense to include it. - public void testQueueExclusiveForUnbind() throws Exception { + @Test public void queueExclusiveForUnbind() throws Exception { altChannel.queueBind(q, "amq.direct", ""); try { channel.queueUnbind(q, "amq.direct", ""); @@ -130,7 +134,7 @@ public void testQueueExclusiveForUnbind() throws Exception { fail("Exclusive queue should be locked for queue unbind from another connection"); } - public void testQueueExclusiveForGet() throws Exception { + @Test public void queueExclusiveForGet() throws Exception { try { channel.basicGet(q, true); } catch (IOException ioe) { diff --git a/test/src/com/rabbitmq/client/test/functional/QueueLease.java b/src/test/java/com/rabbitmq/client/test/functional/QueueLease.java similarity index 83% rename from test/src/com/rabbitmq/client/test/functional/QueueLease.java rename to src/test/java/com/rabbitmq/client/test/functional/QueueLease.java index 055da31ead..fb05ece123 100644 --- a/test/src/com/rabbitmq/client/test/functional/QueueLease.java +++ b/src/test/java/com/rabbitmq/client/test/functional/QueueLease.java @@ -1,26 +1,29 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.IOException; import java.util.HashMap; import java.util.Map; +import org.junit.jupiter.api.Test; + import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Consumer; import com.rabbitmq.client.DefaultConsumer; @@ -41,19 +44,19 @@ public class QueueLease extends BrokerTestCase { * Verify that a queue with the 'x-expires` flag is actually deleted within * a sensible period of time after expiry. */ - public void testQueueExpires() throws IOException, InterruptedException { + @Test public void queueExpires() throws IOException, InterruptedException { verifyQueueExpires(TEST_EXPIRE_QUEUE, true); } /** * Verify that the server does not delete normal queues... ;) */ - public void testDoesNotExpireOthers() throws IOException, + @Test public void doesNotExpireOthers() throws IOException, InterruptedException { verifyQueueExpires(TEST_NORMAL_QUEUE, false); } - public void testExpireMayBeByte() throws IOException { + @Test public void expireMayBeByte() throws IOException { Map args = new HashMap(); args.put("x-expires", (byte)100); @@ -64,7 +67,7 @@ public void testExpireMayBeByte() throws IOException { } } - public void testExpireMayBeShort() throws IOException { + @Test public void expireMayBeShort() throws IOException { Map args = new HashMap(); args.put("x-expires", (short)100); @@ -75,7 +78,7 @@ public void testExpireMayBeShort() throws IOException { } } - public void testExpireMayBeLong() throws IOException { + @Test public void expireMayBeLong() throws IOException { Map args = new HashMap(); args.put("x-expires", 100L); @@ -86,7 +89,7 @@ public void testExpireMayBeLong() throws IOException { } } - public void testExpireMustBeGtZero() throws IOException { + @Test public void expireMustBeGtZero() throws IOException { Map args = new HashMap(); args.put("x-expires", 0); @@ -99,7 +102,7 @@ public void testExpireMustBeGtZero() throws IOException { } } - public void testExpireMustBePositive() throws IOException { + @Test public void expireMustBePositive() throws IOException { Map args = new HashMap(); args.put("x-expires", -10); @@ -116,7 +119,7 @@ public void testExpireMustBePositive() throws IOException { * Verify that the server throws an error if the client redeclares a queue * with mismatching 'x-expires' values. */ - public void testQueueRedeclareEquivalence() throws IOException { + @Test public void queueRedeclareEquivalence() throws IOException { Map args1 = new HashMap(); args1.put("x-expires", 10000); Map args2 = new HashMap(); @@ -134,7 +137,7 @@ public void testQueueRedeclareEquivalence() throws IOException { } } - public void testActiveQueueDeclareExtendsLease() + @Test public void activeQueueDeclareExtendsLease() throws InterruptedException, IOException { Map args = new HashMap(); args.put("x-expires", QUEUE_EXPIRES); @@ -157,7 +160,7 @@ public void testActiveQueueDeclareExtendsLease() } } - public void testPassiveQueueDeclareExtendsLease() + @Test public void passiveQueueDeclareExtendsLease() throws InterruptedException, IOException { Map args = new HashMap(); args.put("x-expires", QUEUE_EXPIRES); @@ -180,7 +183,7 @@ public void testPassiveQueueDeclareExtendsLease() } } - public void testExpiresWithConsumers() + @Test public void expiresWithConsumers() throws InterruptedException, IOException { Map args = new HashMap(); args.put("x-expires", QUEUE_EXPIRES); diff --git a/test/src/com/rabbitmq/client/test/functional/QueueLifecycle.java b/src/test/java/com/rabbitmq/client/test/functional/QueueLifecycle.java similarity index 63% rename from test/src/com/rabbitmq/client/test/functional/QueueLifecycle.java rename to src/test/java/com/rabbitmq/client/test/functional/QueueLifecycle.java index 5e71c8fe8e..51e3e48168 100644 --- a/test/src/com/rabbitmq/client/test/functional/QueueLifecycle.java +++ b/src/test/java/com/rabbitmq/client/test/functional/QueueLifecycle.java @@ -1,30 +1,33 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Consumer; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.test.BrokerTestCase; +import org.junit.jupiter.api.Test; + import java.io.IOException; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeoutException; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.QueueingConsumer; -import com.rabbitmq.client.test.BrokerTestCase; +import static org.junit.jupiter.api.Assertions.fail; /** * Test queue auto-delete and exclusive semantics. @@ -87,7 +90,7 @@ void verifyNotEquivalent(boolean durable, boolean exclusive, * Declare-Ok if the requested queue matches these fields, and MUST * raise a channel exception if not." */ - public void testQueueEquivalence() throws IOException { + @Test public void queueEquivalence() throws IOException { String q = "queue"; channel.queueDeclare(q, false, false, false, null); // equivalent @@ -102,27 +105,27 @@ public void testQueueEquivalence() throws IOException { } // not equivalent in various ways - public void testQueueNonEquivalenceDurable() throws IOException { + @Test public void queueNonEquivalenceDurable() throws IOException { verifyNotEquivalent(true, false, false); } - public void testQueueNonEquivalenceExclusive() throws IOException { + @Test public void queueNonEquivalenceExclusive() throws IOException { verifyNotEquivalent(false, true, false); } - public void testQueueNonEquivalenceAutoDelete() throws IOException { + @Test public void queueNonEquivalenceAutoDelete() throws IOException { verifyNotEquivalent(false, false, true); } // Note that this assumes that auto-deletion is synchronous with // basic.cancel, // which is not actually in the spec. (If it isn't, there's a race here). - public void testQueueAutoDelete() throws IOException { + @Test public void queueAutoDelete() throws IOException { String name = "tempqueue"; channel.queueDeclare(name, false, false, true, null); // now it's there verifyQueue(name, false, false, true, null); - QueueingConsumer consumer = new QueueingConsumer(channel); + Consumer consumer = new DefaultConsumer(channel); String consumerTag = channel.basicConsume(name, consumer); channel.basicCancel(consumerTag); // now it's not .. we hope @@ -135,19 +138,19 @@ public void testQueueAutoDelete() throws IOException { fail("Queue should have been auto-deleted after we removed its only consumer"); } - public void testExclusiveNotAutoDelete() throws IOException { + @Test public void exclusiveNotAutoDelete() throws IOException { String name = "exclusivequeue"; channel.queueDeclare(name, false, true, false, null); // now it's there verifyQueue(name, false, true, false, null); - QueueingConsumer consumer = new QueueingConsumer(channel); + Consumer consumer = new DefaultConsumer(channel); String consumerTag = channel.basicConsume(name, consumer); channel.basicCancel(consumerTag); // and still there, because exclusive no longer implies autodelete verifyQueueExists(name); } - public void testExclusiveGoesWithConnection() throws IOException { + @Test public void exclusiveGoesWithConnection() throws IOException, TimeoutException { String name = "exclusivequeue2"; channel.queueDeclare(name, false, true, false, null); // now it's there @@ -158,7 +161,7 @@ public void testExclusiveGoesWithConnection() throws IOException { verifyQueueMissing(name); } - public void testArgumentArrays() throws IOException { + @Test public void argumentArrays() throws IOException { Map args = new HashMap(); String[] arr = new String[]{"foo", "bar", "baz"}; args.put("my-key", arr); @@ -166,4 +169,36 @@ public void testArgumentArrays() throws IOException { channel.queueDeclare(queueName, true, true, false, args); verifyQueue(queueName, true, true, false, args); } + + @Test public void queueNamesLongerThan255Characters() throws IOException { + String q = new String(new byte[300]).replace('\u0000', 'x'); + try { + channel.queueDeclare(q, false, false, false, null); + fail("queueDeclare should have failed"); + } catch (IllegalArgumentException ignored) { + // expected + } + } + + @Test public void singleLineFeedStrippedFromQueueName() throws IOException { + channel.queueDeclare("que\nue_test", false, false, true, null); + verifyQueue(NAME_STRIPPED, false, false, true, null); + } + + @Test public void multipleLineFeedsStrippedFromQueueName() throws IOException { + channel.queueDeclare("que\nue_\ntest\n", false, false, true, null); + verifyQueue(NAME_STRIPPED, false, false, true, null); + } + + @Test public void multipleLineFeedAndCarriageReturnsStrippedFromQueueName() throws IOException { + channel.queueDeclare("q\ru\ne\r\nue_\ntest\n\r", false, false, true, null); + verifyQueue(NAME_STRIPPED, false, false, true, null); + } + + static final String NAME_STRIPPED = "queue_test"; + + @Override + protected void releaseResources() throws IOException { + channel.queueDelete(NAME_STRIPPED); + } } diff --git a/test/src/com/rabbitmq/client/test/functional/QueueSizeLimit.java b/src/test/java/com/rabbitmq/client/test/functional/QueueSizeLimit.java similarity index 74% rename from test/src/com/rabbitmq/client/test/functional/QueueSizeLimit.java rename to src/test/java/com/rabbitmq/client/test/functional/QueueSizeLimit.java index 5b62a022cc..650132ed86 100644 --- a/test/src/com/rabbitmq/client/test/functional/QueueSizeLimit.java +++ b/src/test/java/com/rabbitmq/client/test/functional/QueueSizeLimit.java @@ -1,30 +1,36 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.test.BrokerTestCase; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.test.BrokerTestCase; + /** * Test queue max length limit. */ @@ -33,7 +39,7 @@ public class QueueSizeLimit extends BrokerTestCase { private final int MAXMAXLENGTH = 3; private final String q = "queue-maxlength"; - public void testQueueSize() throws IOException, InterruptedException { + @Test public void queueSize() throws IOException, InterruptedException { for (int maxLen = 0; maxLen <= MAXMAXLENGTH; maxLen ++){ setupNonDlxTest(maxLen, false); assertHead(maxLen, "msg2", q); @@ -41,7 +47,7 @@ public void testQueueSize() throws IOException, InterruptedException { } } - public void testQueueSizeUnacked() throws IOException, InterruptedException { + @Test public void queueSizeUnacked() throws IOException, InterruptedException { for (int maxLen = 0; maxLen <= MAXMAXLENGTH; maxLen ++){ setupNonDlxTest(maxLen, true); assertHead(maxLen > 0 ? 1 : 0, "msg" + (maxLen + 1), q); @@ -49,7 +55,7 @@ public void testQueueSizeUnacked() throws IOException, InterruptedException { } } - public void testQueueSizeDlx() throws IOException, InterruptedException { + @Test public void queueSizeDlx() throws IOException, InterruptedException { for (int maxLen = 0; maxLen <= MAXMAXLENGTH; maxLen ++){ setupDlxTest(maxLen, false); assertHead(1, "msg1", "DLQ"); @@ -58,7 +64,7 @@ public void testQueueSizeDlx() throws IOException, InterruptedException { } } - public void testQueueSizeUnackedDlx() throws IOException, InterruptedException { + @Test public void queueSizeUnackedDlx() throws IOException, InterruptedException { for (int maxLen = 0; maxLen <= MAXMAXLENGTH; maxLen ++){ setupDlxTest(maxLen, true); assertHead(maxLen > 0 ? 0 : 1, "msg1", "DLQ"); @@ -67,7 +73,7 @@ public void testQueueSizeUnackedDlx() throws IOException, InterruptedException } } - public void testRequeue() throws IOException, InterruptedException { + @Test public void requeue() throws IOException, InterruptedException { for (int maxLen = 1; maxLen <= MAXMAXLENGTH; maxLen ++) { declareQueue(maxLen, false); setupRequeueTest(maxLen); @@ -76,7 +82,7 @@ public void testRequeue() throws IOException, InterruptedException { } } - public void testRequeueWithDlx() throws IOException, InterruptedException { + @Test public void requeueWithDlx() throws IOException, InterruptedException { for (int maxLen = 1; maxLen <= MAXMAXLENGTH; maxLen ++) { declareQueue(maxLen, true); setupRequeueTest(maxLen); diff --git a/test/src/com/rabbitmq/client/test/functional/Recover.java b/src/test/java/com/rabbitmq/client/test/functional/Recover.java similarity index 50% rename from test/src/com/rabbitmq/client/test/functional/Recover.java rename to src/test/java/com/rabbitmq/client/test/functional/Recover.java index 24d6b7d76a..7e1b62191f 100644 --- a/test/src/com/rabbitmq/client/test/functional/Recover.java +++ b/src/test/java/com/rabbitmq/client/test/functional/Recover.java @@ -1,30 +1,34 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import java.util.Arrays; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.QueueingConsumer; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Command; -import com.rabbitmq.client.ShutdownSignalException; +import com.rabbitmq.client.*; +import org.junit.jupiter.api.Test; import com.rabbitmq.client.test.BrokerTestCase; @@ -52,26 +56,31 @@ void verifyRedeliverOnRecover(RecoverCallback call) channel.basicConsume(queue, false, consumer); // require acks. channel.basicPublish("", queue, new AMQP.BasicProperties.Builder().build(), body); QueueingConsumer.Delivery delivery = consumer.nextDelivery(); - assertTrue("consumed message body not as sent", - Arrays.equals(body, delivery.getBody())); + assertTrue(Arrays.equals(body, delivery.getBody()), "consumed message body not as sent"); // Don't ack it, and get it redelivered to the same consumer call.recover(channel); QueueingConsumer.Delivery secondDelivery = consumer.nextDelivery(5000); - assertNotNull("timed out waiting for redelivered message", secondDelivery); - assertTrue("consumed (redelivered) message body not as sent", - Arrays.equals(body, delivery.getBody())); + assertNotNull(secondDelivery, "timed out waiting for redelivered message"); + assertTrue(Arrays.equals(body, delivery.getBody()), "consumed (redelivered) message body not as sent"); } void verifyNoRedeliveryWithAutoAck(RecoverCallback call) throws IOException, InterruptedException { - QueueingConsumer consumer = new QueueingConsumer(channel); + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference bodyReference = new AtomicReference(); + Consumer consumer = new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + bodyReference.set(body); + latch.countDown(); + } + }; channel.basicConsume(queue, true, consumer); // auto ack. channel.basicPublish("", queue, new AMQP.BasicProperties.Builder().build(), body); - QueueingConsumer.Delivery delivery = consumer.nextDelivery(); - assertTrue("consumed message body not as sent", - Arrays.equals(body, delivery.getBody())); + assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertTrue(Arrays.equals(body, bodyReference.get()), "consumed message body not as sent"); call.recover(channel); - assertNull("should be no message available", channel.basicGet(queue, true)); + assertNull(channel.basicGet(queue, true), "should be no message available"); } final RecoverCallback recoverSync = new RecoverCallback() { @@ -86,21 +95,21 @@ public void recover(Channel channel) throws IOException { } }; - public void testRedeliveryOnRecover() throws IOException, InterruptedException { + @Test public void redeliveryOnRecover() throws IOException, InterruptedException { verifyRedeliverOnRecover(recoverSync); } - public void testRedeliverOnRecoverConvenience() + @Test public void redeliverOnRecoverConvenience() throws IOException, InterruptedException { verifyRedeliverOnRecover(recoverSyncConvenience); } - public void testNoRedeliveryWithAutoAck() + @Test public void noRedeliveryWithAutoAck() throws IOException, InterruptedException { verifyNoRedeliveryWithAutoAck(recoverSync); } - public void testRequeueFalseNotSupported() throws Exception { + @Test public void requeueFalseNotSupported() throws Exception { try { channel.basicRecover(false); fail("basicRecover(false) should not be supported"); diff --git a/src/test/java/com/rabbitmq/client/test/functional/Reject.java b/src/test/java/com/rabbitmq/client/test/functional/Reject.java new file mode 100644 index 0000000000..eaf8b6e04a --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/Reject.java @@ -0,0 +1,80 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.test.TestUtils; +import com.rabbitmq.client.test.TestUtils.CallableFunction; +import java.util.Collections; + +import java.util.UUID; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.QueueingConsumer; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class Reject extends AbstractRejectTest +{ + + public static Object[] reject() { + return new Object[] { + (CallableFunction) channel -> { + String q = UUID.randomUUID().toString(); + channel.queueDeclare(q, true, false, false, Collections.singletonMap("x-queue-type", "quorum")); + return q; + }, + (CallableFunction) channel -> { + String q = UUID.randomUUID().toString(); + channel.queueDeclare(q, true, false, false, Collections.singletonMap("x-queue-type", "classic")); + return q; + }}; + } + + @ParameterizedTest + @MethodSource + public void reject(TestUtils.CallableFunction queueCreator) + throws Exception + { + String q = queueCreator.apply(channel); + + channel.confirmSelect(); + + byte[] m1 = "1".getBytes(); + byte[] m2 = "2".getBytes(); + + basicPublishVolatile(m1, q); + basicPublishVolatile(m2, q); + + channel.waitForConfirmsOrDie(1000); + + long tag1 = checkDelivery(channel.basicGet(q, false), m1, false); + long tag2 = checkDelivery(channel.basicGet(q, false), m2, false); + QueueingConsumer c = new QueueingConsumer(secondaryChannel); + String consumerTag = secondaryChannel.basicConsume(q, false, c); + channel.basicReject(tag2, true); + long tag3 = checkDelivery(c.nextDelivery(), m2, true); + secondaryChannel.basicCancel(consumerTag); + secondaryChannel.basicReject(tag3, false); + assertNull(channel.basicGet(q, false)); + channel.basicAck(tag1, false); + channel.basicReject(tag3, false); + expectError(AMQP.PRECONDITION_FAILED); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/RequeueOnChannelClose.java b/src/test/java/com/rabbitmq/client/test/functional/RequeueOnChannelClose.java new file mode 100644 index 0000000000..855ab24f62 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/RequeueOnChannelClose.java @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.functional; + +import java.io.IOException; + +public class RequeueOnChannelClose extends RequeueOnClose +{ + + protected void open() throws IOException + { + openChannel(); + } + + protected void close() throws IOException + { + closeChannel(); + } + +} diff --git a/test/src/com/rabbitmq/client/test/functional/RequeueOnClose.java b/src/test/java/com/rabbitmq/client/test/functional/RequeueOnClose.java similarity index 74% rename from test/src/com/rabbitmq/client/test/functional/RequeueOnClose.java rename to src/test/java/com/rabbitmq/client/test/functional/RequeueOnClose.java index 91f33d64af..932a427e17 100644 --- a/test/src/com/rabbitmq/client/test/functional/RequeueOnClose.java +++ b/src/test/java/com/rabbitmq/client/test/functional/RequeueOnClose.java @@ -1,24 +1,28 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + + import com.rabbitmq.client.test.BrokerTestCase; import java.io.IOException; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeoutException; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; @@ -38,17 +42,17 @@ public abstract class RequeueOnClose private static final String Q = "RequeueOnClose"; private static final int MESSAGE_COUNT = 2000; - protected abstract void open() throws IOException; + protected abstract void open() throws IOException, TimeoutException; protected abstract void close() throws IOException; - protected void setUp() + public void setUp() throws IOException { // Override to disable the default behaviour from BrokerTestCase. } - protected void tearDown() + public void tearDown() throws IOException { // Override to disable the default behaviour from BrokerTestCase. @@ -70,8 +74,7 @@ private GetResponse getMessage() } private void publishAndGet(int count, boolean doAck) - throws IOException, InterruptedException - { + throws IOException, InterruptedException, TimeoutException { openConnection(); for (int repeat = 0; repeat < count; repeat++) { open(); @@ -81,9 +84,9 @@ private void publishAndGet(int count, boolean doAck) close(); open(); if (doAck) { - assertNull("Expected missing second basicGet (repeat="+repeat+")", getMessage()); + assertNull(getMessage(), "Expected missing second basicGet (repeat="+repeat+")"); } else { - assertNotNull("Expected present second basicGet (repeat="+repeat+")", getMessage()); + assertNotNull(getMessage(), "Expected present second basicGet (repeat="+repeat+")"); } close(); } @@ -94,7 +97,7 @@ private void publishAndGet(int count, boolean doAck) * Test we don't requeue acknowledged messages (using get) * @throws Exception untested */ - public void testNormal() throws Exception + @Test public void normal() throws Exception { publishAndGet(3, true); } @@ -103,7 +106,7 @@ public void testNormal() throws Exception * Test we requeue unacknowledged messages (using get) * @throws Exception untested */ - public void testRequeueing() throws Exception + @Test public void requeueing() throws Exception { publishAndGet(3, false); } @@ -112,7 +115,7 @@ public void testRequeueing() throws Exception * Test we requeue unacknowledged message (using consumer) * @throws Exception untested */ - public void testRequeueingConsumer() throws Exception + @Test public void requeueingConsumer() throws Exception { openConnection(); open(); @@ -128,8 +131,7 @@ public void testRequeueingConsumer() throws Exception } private void publishLotsAndGet() - throws IOException, InterruptedException, ShutdownSignalException - { + throws IOException, InterruptedException, ShutdownSignalException, TimeoutException { openConnection(); open(); channel.queueDeclare(Q, false, false, false, null); @@ -144,10 +146,10 @@ private void publishLotsAndGet() close(); open(); for (int i = 0; i < MESSAGE_COUNT; i++) { - assertNotNull("only got " + i + " out of " + MESSAGE_COUNT + - " messages", channel.basicGet(Q, true)); + assertNotNull(channel.basicGet(Q, true), "only got " + i + " out of " + MESSAGE_COUNT + + " messages"); } - assertNull("got more messages than " + MESSAGE_COUNT + " expected", channel.basicGet(Q, true)); + assertNull(channel.basicGet(Q, true), "got more messages than " + MESSAGE_COUNT + " expected"); channel.queueDelete(Q); close(); closeConnection(); @@ -157,7 +159,7 @@ private void publishLotsAndGet() * Test close while consuming many messages successfully requeues unacknowledged messages * @throws Exception untested */ - public void testRequeueInFlight() throws Exception + @Test public void requeueInFlight() throws Exception { for (int i = 0; i < 5; i++) { publishLotsAndGet(); @@ -168,7 +170,7 @@ public void testRequeueInFlight() throws Exception * Test close while consuming partially not acked with cancel successfully requeues unacknowledged messages * @throws Exception untested */ - public void testRequeueInFlightConsumerNoAck() throws Exception + @Test public void requeueInFlightConsumerNoAck() throws Exception { for (int i = 0; i < 5; i++) { publishLotsAndConsumeSome(false, true); @@ -179,7 +181,7 @@ public void testRequeueInFlightConsumerNoAck() throws Exception * Test close while consuming partially acked with cancel successfully requeues unacknowledged messages * @throws Exception untested */ - public void testRequeueInFlightConsumerAck() throws Exception + @Test public void requeueInFlightConsumerAck() throws Exception { for (int i = 0; i < 5; i++) { publishLotsAndConsumeSome(true, true); @@ -190,7 +192,7 @@ public void testRequeueInFlightConsumerAck() throws Exception * Test close while consuming partially not acked without cancel successfully requeues unacknowledged messages * @throws Exception untested */ - public void testRequeueInFlightConsumerNoAckNoCancel() throws Exception + @Test public void requeueInFlightConsumerNoAckNoCancel() throws Exception { for (int i = 0; i < 5; i++) { publishLotsAndConsumeSome(false, false); @@ -201,7 +203,7 @@ public void testRequeueInFlightConsumerNoAckNoCancel() throws Exception * Test close while consuming partially acked without cancel successfully requeues unacknowledged messages * @throws Exception untested */ - public void testRequeueInFlightConsumerAckNoCancel() throws Exception + @Test public void requeueInFlightConsumerAckNoCancel() throws Exception { for (int i = 0; i < 5; i++) { publishLotsAndConsumeSome(true, false); @@ -211,8 +213,7 @@ public void testRequeueInFlightConsumerAckNoCancel() throws Exception private static final int MESSAGES_TO_CONSUME = 20; private void publishLotsAndConsumeSome(boolean ack, boolean cancelBeforeFinish) - throws IOException, InterruptedException, ShutdownSignalException - { + throws IOException, InterruptedException, ShutdownSignalException, TimeoutException { openConnection(); open(); channel.queueDeclare(Q, false, false, false, null); @@ -231,14 +232,13 @@ private void publishLotsAndConsumeSome(boolean ack, boolean cancelBeforeFinish) open(); int requeuedMsgCount = (ack) ? MESSAGE_COUNT - MESSAGES_TO_CONSUME : MESSAGE_COUNT; for (int i = 0; i < requeuedMsgCount; i++) { - assertNotNull("only got " + i + " out of " + requeuedMsgCount + " messages", - channel.basicGet(Q, true)); + assertNotNull(channel.basicGet(Q, true), "only got " + i + " out of " + requeuedMsgCount + " messages"); } int countMoreMsgs = 0; while (null != channel.basicGet(Q, true)) { countMoreMsgs++; } - assertTrue("got " + countMoreMsgs + " more messages than " + requeuedMsgCount + " expected", 0==countMoreMsgs); + assertTrue(0==countMoreMsgs, "got " + countMoreMsgs + " more messages than " + requeuedMsgCount + " expected"); channel.queueDelete(Q); close(); closeConnection(); diff --git a/src/test/java/com/rabbitmq/client/test/functional/RequeueOnConnectionClose.java b/src/test/java/com/rabbitmq/client/test/functional/RequeueOnConnectionClose.java new file mode 100644 index 0000000000..71e0650588 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/RequeueOnConnectionClose.java @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.functional; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +public class RequeueOnConnectionClose extends RequeueOnClose +{ + + protected void open() throws IOException, TimeoutException { + openConnection(); + openChannel(); + } + + protected void close() throws IOException + { + closeConnection(); + } + +} diff --git a/test/src/com/rabbitmq/client/test/functional/Routing.java b/src/test/java/com/rabbitmq/client/test/functional/Routing.java similarity index 66% rename from test/src/com/rabbitmq/client/test/functional/Routing.java rename to src/test/java/com/rabbitmq/client/test/functional/Routing.java index a45122cf6c..1710a817fc 100644 --- a/test/src/com/rabbitmq/client/test/functional/Routing.java +++ b/src/test/java/com/rabbitmq/client/test/functional/Routing.java @@ -1,36 +1,45 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.AlreadyClosedException; -import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.ReturnListener; -import com.rabbitmq.utility.BlockingCell; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; +import com.rabbitmq.client.test.TestUtils.BrokerVersion; +import com.rabbitmq.client.test.TestUtils.BrokerVersionAtLeast; import java.io.IOException; -import java.util.List; +import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.AlreadyClosedException; +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.ReturnListener; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.utility.BlockingCell; + public class Routing extends BrokerTestCase { @@ -39,6 +48,7 @@ public class Routing extends BrokerTestCase protected final String Q2 = "bar"; private volatile BlockingCell returnCell; + private static final int TIMEOUT = (int) Duration.ofSeconds(10).toMillis(); protected void createResources() throws IOException { channel.exchangeDeclare(E, "direct"); @@ -82,7 +92,7 @@ private void checkGet(String queue, boolean messageExpected) * of the spec. See the doc for the "queue" and "routing key" * fields of queue.bind. */ - public void testMRDQRouting() + @Test public void mRDQRouting() throws IOException { bind(Q1, "baz"); //Q1, "baz" @@ -100,7 +110,7 @@ public void testMRDQRouting() * NOT receive duplicate copies of a message that matches both * bindings. */ - public void testDoubleBinding() + @Test public void doubleBinding() throws IOException { channel.queueBind(Q1, "amq.topic", "x.#"); @@ -116,7 +126,7 @@ public void testDoubleBinding() checkGet(Q1, false); } - public void testFanoutRouting() throws Exception { + @Test public void fanoutRouting() throws Exception { List queues = new ArrayList(); @@ -139,7 +149,7 @@ public void testFanoutRouting() throws Exception { } } - public void testTopicRouting() throws Exception { + @Test public void topicRouting() throws Exception { List queues = new ArrayList(); @@ -159,11 +169,12 @@ public void testTopicRouting() throws Exception { } } - public void testHeadersRouting() throws Exception { + @Test public void headersRouting() throws Exception { Map spec = new HashMap(); spec.put("h1", "12345"); spec.put("h2", "bar"); spec.put("h3", null); + spec.put("x-key-1", "bindings starting with x- get filtered out"); spec.put("x-match", "all"); channel.queueBind(Q1, "amq.match", "", spec); spec.put("x-match", "any"); @@ -220,6 +231,10 @@ public void testHeadersRouting() throws Exception { map.put("h2", "quux"); channel.basicPublish("amq.match", "", props.build(), "8".getBytes()); + map.clear(); + map.put("x-key-1", "bindings starting with x- get filtered out"); + channel.basicPublish("amq.match", "", props.build(), "9".getBytes()); + checkGet(Q1, true); // 4 checkGet(Q1, false); @@ -234,13 +249,55 @@ public void testHeadersRouting() throws Exception { checkGet(Q2, false); } - public void testBasicReturn() throws IOException { + @Test + @BrokerVersionAtLeast(BrokerVersion.RABBITMQ_3_10) + public void headersWithXRouting() throws Exception { + Map spec = new HashMap(); + spec.put("x-key-1", "value-1"); + spec.put("x-key-2", "value-2"); + spec.put("x-match", "all-with-x"); + channel.queueBind(Q1, "amq.match", "", spec); + spec.put("x-match", "any-with-x"); + channel.queueBind(Q2, "amq.match", "", spec); + + AMQP.BasicProperties.Builder props = new AMQP.BasicProperties.Builder(); + channel.basicPublish("amq.match", "", props.build(), "0".getBytes()); + + Map map = new HashMap(); + props.headers(map); + + map.clear(); + map.put("x-key-1", "value-1"); + channel.basicPublish("amq.match", "", props.build(), "1".getBytes()); + + map.clear(); + map.put("x-key-1", "value-1"); + map.put("x-key-2", "value-2"); + channel.basicPublish("amq.match", "", props.build(), "2".getBytes()); + + map.clear(); + map.put("x-key-1", "value-1"); + map.put("x-key-2", "value-2"); + map.put("x-key-3", "value-3"); + channel.basicPublish("amq.match", "", props.build(), "3".getBytes()); + + checkGet(Q1, true); // 2 + checkGet(Q1, true); // 3 + checkGet(Q1, false); + + checkGet(Q2, true); // 1 + checkGet(Q2, true); // 2 + checkGet(Q2, true); // 3 + checkGet(Q2, false); + } + + @Test public void basicReturn() throws Exception { channel.addReturnListener(makeReturnListener()); returnCell = new BlockingCell(); //returned 'mandatory' publish channel.basicPublish("", "unknown", true, false, null, "mandatory1".getBytes()); - checkReturn(AMQP.NO_ROUTE); + checkReturn(); //routed 'mandatory' publish channel.basicPublish("", Q1, true, false, null, "mandatory2".getBytes()); @@ -258,7 +315,7 @@ public void testBasicReturn() throws IOException { } } - public void testBasicReturnTransactional() throws IOException { + @Test public void basicReturnTransactional() throws Exception { channel.txSelect(); channel.addReturnListener(makeReturnListener()); returnCell = new BlockingCell(); @@ -270,39 +327,31 @@ public void testBasicReturnTransactional() throws IOException { fail("basic.return issued prior to tx.commit"); } catch (TimeoutException toe) {} channel.txCommit(); - checkReturn(AMQP.NO_ROUTE); + checkReturn(); //routed 'mandatory' publish channel.basicPublish("", Q1, true, false, null, "mandatory2".getBytes()); channel.txCommit(); assertNotNull(channel.basicGet(Q1, true)); - //returned 'mandatory' publish when message is routable on - //publish but not on commit - channel.basicPublish("", Q1, true, false, null, "mandatory2".getBytes()); - channel.queueDelete(Q1); - channel.txCommit(); - checkReturn(AMQP.NO_ROUTE); - channel.queueDeclare(Q1, false, false, false, null); + if (beforeMessageContainers()) { + //returned 'mandatory' publish when message is routable on + //publish but not on commit + channel.basicPublish("", Q1, true, false, null, "mandatory2".getBytes()); + channel.queueDelete(Q1); + channel.txCommit(); + checkReturn(); + channel.queueDeclare(Q1, false, false, false, null); + } } protected ReturnListener makeReturnListener() { - return new ReturnListener() { - public void handleReturn(int replyCode, - String replyText, - String exchange, - String routingKey, - AMQP.BasicProperties properties, - byte[] body) - throws IOException { - Routing.this.returnCell.set(replyCode); - } - }; + return (replyCode, replyText, exchange, routingKey, properties, body) -> Routing.this.returnCell.set(replyCode); } - protected void checkReturn(int replyCode) { - assertEquals((int)returnCell.uninterruptibleGet(), AMQP.NO_ROUTE); - returnCell = new BlockingCell(); + protected void checkReturn() throws TimeoutException { + assertEquals((int)returnCell.uninterruptibleGet(TIMEOUT), AMQP.NO_ROUTE); + returnCell = new BlockingCell<>(); } } diff --git a/test/src/com/rabbitmq/client/test/functional/SaslMechanisms.java b/src/test/java/com/rabbitmq/client/test/functional/SaslMechanisms.java similarity index 66% rename from test/src/com/rabbitmq/client/test/functional/SaslMechanisms.java rename to src/test/java/com/rabbitmq/client/test/functional/SaslMechanisms.java index 3f9054352c..4bc6b9ebd3 100644 --- a/test/src/com/rabbitmq/client/test/functional/SaslMechanisms.java +++ b/src/test/java/com/rabbitmq/client/test/functional/SaslMechanisms.java @@ -1,35 +1,34 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.AuthenticationFailureException; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.LongString; -import com.rabbitmq.client.PossibleAuthenticationFailureException; -import com.rabbitmq.client.SaslConfig; -import com.rabbitmq.client.SaslMechanism; -import com.rabbitmq.client.impl.AMQConnection; -import com.rabbitmq.client.impl.LongStringHelper; -import com.rabbitmq.client.test.BrokerTestCase; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.util.Arrays; import java.util.Map; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.*; +import com.rabbitmq.client.test.TestUtils; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.impl.AMQConnection; +import com.rabbitmq.client.impl.LongStringHelper; +import com.rabbitmq.client.test.BrokerTestCase; public class SaslMechanisms extends BrokerTestCase { private String[] mechanisms; @@ -69,21 +68,19 @@ public SaslMechanism getSaslMechanism(String[] mechanisms) { } } - // TODO test gibberish examples. ATM the server is not very robust. - - public void testPlainLogin() throws IOException { + @Test public void plainLogin() throws IOException, TimeoutException { loginOk("PLAIN", new byte[][] {"\0guest\0guest".getBytes()} ); loginBad("PLAIN", new byte[][] {"\0guest\0wrong".getBytes()} ); } - public void testAMQPlainLogin() throws IOException { + @Test public void aMQPlainLogin() throws IOException, TimeoutException { // guest / guest loginOk("AMQPLAIN", new byte[][] {{5,76,79,71,73,78,83,0,0,0,5,103,117,101,115,116,8,80,65,83,83,87,79,82,68,83,0,0,0,5,103,117,101,115,116}} ); // guest / wrong loginBad("AMQPLAIN", new byte[][] {{5,76,79,71,73,78,83,0,0,0,5,103,117,101,115,116,8,80,65,83,83,87,79,82,68,83,0,0,0,5,119,114,111,110,103}} ); } - public void testCRLogin() throws IOException { + @Test public void cRLogin() throws IOException, TimeoutException { // Make sure mechanisms is populated loginOk("PLAIN", new byte[][] {"\0guest\0guest".getBytes()} ); @@ -94,15 +91,24 @@ public void testCRLogin() throws IOException { } } - public void testConnectionCloseAuthFailureUsername() throws IOException { + @Test public void connectionCloseAuthFailureUsername() throws IOException, TimeoutException { connectionCloseAuthFailure("incorrect-username", "incorrect-password"); } - public void testConnectionCloseAuthFailurePassword() throws IOException { + @Test public void connectionCloseAuthFailurePassword() throws IOException, TimeoutException { connectionCloseAuthFailure(connectionFactory.getUsername(), "incorrect-password"); } - public void connectionCloseAuthFailure(String username, String password) throws IOException { + @Test + @TestUtils.BrokerVersionAtLeast(TestUtils.BrokerVersion.RABBITMQ_4_0) + public void anonymousShouldSucceed() throws Exception { + ConnectionFactory factory = TestUtils.connectionFactory(); + factory.setSaslConfig(DefaultSaslConfig.ANONYMOUS); + Connection connection = factory.newConnection(); + connection.close(); + } + + public void connectionCloseAuthFailure(String username, String password) throws IOException, TimeoutException { String failDetail = "for username " + username + " and password " + password; try { Connection conn = connectionWithoutCapabilities(username, password); @@ -117,8 +123,9 @@ public void connectionCloseAuthFailure(String username, String password) throws // start a connection without capabilities, causing authentication failures // to be reported by the broker by closing the connection - private Connection connectionWithoutCapabilities(String username, String password) throws IOException { - ConnectionFactory customFactory = connectionFactory.clone(); + private Connection connectionWithoutCapabilities(String username, String password) + throws IOException, TimeoutException { + ConnectionFactory customFactory = TestUtils.connectionFactory(); customFactory.setUsername(username); customFactory.setPassword(password); Map customProperties = AMQConnection.defaultClientProperties(); @@ -127,14 +134,14 @@ private Connection connectionWithoutCapabilities(String username, String passwor return customFactory.newConnection(); } - private void loginOk(String name, byte[][] responses) throws IOException { - ConnectionFactory factory = new ConnectionFactory(); + private void loginOk(String name, byte[][] responses) throws IOException, TimeoutException { + ConnectionFactory factory = TestUtils.connectionFactory(); factory.setSaslConfig(new Config(name, responses)); Connection connection = factory.newConnection(); connection.close(); } - private void loginBad(String name, byte[][] responses) throws IOException { + private void loginBad(String name, byte[][] responses) throws IOException, TimeoutException { try { loginOk(name, responses); fail("Login succeeded!"); diff --git a/test/src/com/rabbitmq/client/test/functional/TTLHandling.java b/src/test/java/com/rabbitmq/client/test/functional/TTLHandling.java similarity index 70% rename from test/src/com/rabbitmq/client/test/functional/TTLHandling.java rename to src/test/java/com/rabbitmq/client/test/functional/TTLHandling.java index 0756ec00ea..a362863f1f 100644 --- a/test/src/com/rabbitmq/client/test/functional/TTLHandling.java +++ b/src/test/java/com/rabbitmq/client/test/functional/TTLHandling.java @@ -1,12 +1,35 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.test.functional; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import com.rabbitmq.client.ShutdownSignalException; +import java.io.IOException; + +import org.junit.jupiter.api.Test; + import com.rabbitmq.client.AMQP; import com.rabbitmq.client.GetResponse; import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.test.BrokerTestCase; -import java.io.IOException; - public abstract class TTLHandling extends BrokerTestCase { protected static final String TTL_EXCHANGE = "ttl.exchange"; @@ -25,7 +48,7 @@ protected void releaseResources() throws IOException { this.channel.exchangeDelete(TTL_EXCHANGE); } - public void testMultipleTTLTypes() throws IOException { + @Test public void multipleTTLTypes() throws IOException { final Object[] args = { (((byte)200) & (0xff)), (short)200, 200, 200L }; for (Object ttl : args) { try { @@ -38,37 +61,43 @@ public void testMultipleTTLTypes() throws IOException { } } - public void testInvalidTypeUsedInTTL() throws Exception { + @Test public void invalidTypeUsedInTTL() throws Exception { try { declareAndBindQueue("foobar"); publishAndSync(MSG[0]); fail("Should not be able to set TTL using non-numeric values"); } catch (IOException e) { checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); + } catch (ShutdownSignalException e) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); } } - public void testTrailingCharsUsedInTTL() throws Exception { + @Test public void trailingCharsUsedInTTL() throws Exception { try { declareAndBindQueue("10000foobar"); publishAndSync(MSG[0]); fail("Should not be able to set TTL using non-numeric values"); } catch (IOException e) { checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); + } catch (ShutdownSignalException e) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); } } - public void testTTLMustBePositive() throws Exception { + @Test public void tTLMustBePositive() { try { declareAndBindQueue(-10); publishAndSync(MSG[0]); fail("Should not be able to set TTL using negative values"); } catch (IOException e) { checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); + } catch (ShutdownSignalException e) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); } } - public void testTTLAllowZero() throws Exception { + @Test public void tTLAllowZero() throws Exception { try { declareQueue(0); publishAndSync(MSG[0]); @@ -77,16 +106,16 @@ public void testTTLAllowZero() throws Exception { } } - public void testMessagesExpireWhenUsingBasicGet() throws Exception { + @Test public void messagesExpireWhenUsingBasicGet() throws Exception { declareAndBindQueue(200); publish(MSG[0]); Thread.sleep(1000); String what = get(); - assertNull("expected message " + what + " to have been removed", what); + assertNull(what, "expected message " + what + " to have been removed"); } - public void testPublishAndGetWithExpiry() throws Exception { + @Test public void publishAndGetWithExpiry() throws Exception { declareAndBindQueue(200); publish(MSG[0]); @@ -102,7 +131,7 @@ public void testPublishAndGetWithExpiry() throws Exception { assertNull(get()); } - public void testTransactionalPublishWithGet() throws Exception { + @Test public void transactionalPublishWithGet() throws Exception { declareAndBindQueue(100); this.channel.txSelect(); @@ -120,11 +149,11 @@ public void testTransactionalPublishWithGet() throws Exception { assertNull(get()); } - public void testExpiryWithRequeue() throws Exception { - declareAndBindQueue(200); + @Test public void expiryWithRequeue() throws Exception { + declareAndBindQueue(400); publish(MSG[0]); - Thread.sleep(100); + Thread.sleep(200); publish(MSG[1]); publish(MSG[2]); @@ -134,7 +163,7 @@ public void testExpiryWithRequeue() throws Exception { closeChannel(); openChannel(); - Thread.sleep(150); + Thread.sleep(300); expectBodyAndRemainingMessages(MSG[1], 1); expectBodyAndRemainingMessages(MSG[2], 0); } @@ -142,7 +171,7 @@ public void testExpiryWithRequeue() throws Exception { /* * Test expiry of re-queued messages after being consumed instantly */ - public void testExpiryWithReQueueAfterConsume() throws Exception { + @Test public void expiryWithReQueueAfterConsume() throws Exception { declareAndBindQueue(100); QueueingConsumer c = new QueueingConsumer(channel); channel.basicConsume(TTL_QUEUE_NAME, c); @@ -154,10 +183,10 @@ public void testExpiryWithReQueueAfterConsume() throws Exception { Thread.sleep(150); openChannel(); - assertNull("Re-queued message not expired", get()); + assertNull(get(), "Re-queued message not expired"); } - public void testZeroTTLDelivery() throws Exception { + @Test public void zeroTTLDelivery() throws Exception { declareAndBindQueue(0); // when there is no consumer, message should expire diff --git a/test/src/com/rabbitmq/client/test/functional/Tables.java b/src/test/java/com/rabbitmq/client/test/functional/Tables.java similarity index 69% rename from test/src/com/rabbitmq/client/test/functional/Tables.java rename to src/test/java/com/rabbitmq/client/test/functional/Tables.java index 5e5e3351df..b79ae1592d 100644 --- a/test/src/com/rabbitmq/client/test/functional/Tables.java +++ b/src/test/java/com/rabbitmq/client/test/functional/Tables.java @@ -1,47 +1,51 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.impl.LongStringHelper; -import com.rabbitmq.client.AMQP.BasicProperties; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Map; -import java.util.List; +import java.util.HashMap; import java.util.Iterator; -import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.Set; -import java.util.HashMap; -import java.math.BigDecimal; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.AMQP.BasicProperties; +import com.rabbitmq.client.impl.LongStringHelper; +import com.rabbitmq.client.test.BrokerTestCase; public class Tables extends BrokerTestCase { - public void testTypes() throws IOException { + @Test public void types() throws IOException { Map table = new HashMap(); Map subTable = new HashMap(); subTable.put("key", 1); table.put("S", LongStringHelper.asLongString("string")); - table.put("I", new Integer(1)); + table.put("I", Integer.valueOf(1)); table.put("D", new BigDecimal("1.1")); table.put("T", new java.util.Date(1000000)); table.put("F", subTable); @@ -83,18 +87,17 @@ private static void assertMapsEqual(Map a, Object va = a.get(k); Object vb = b.get(k); if (va instanceof byte[] && vb instanceof byte[]) { - assertTrue("unequal entry for key " + k, - Arrays.equals((byte[])va, (byte[])vb)); + assertTrue(Arrays.equals((byte[])va, (byte[])vb), "unequal entry for key " + k); } else if (va instanceof List && vb instanceof List) { Iterator vbi = ((List)vb).iterator(); for (Object vaEntry : (List)va) { Object vbEntry = vbi.next(); - assertEquals("arrays unequal at key " + k, vaEntry, vbEntry); + assertEquals(vaEntry, vbEntry, "arrays unequal at key " + k); } } else { - assertEquals("unequal entry for key " + k, va, vb); + assertEquals(va, vb, "unequal entry for key " + k); } } } diff --git a/src/test/java/com/rabbitmq/client/test/functional/TopologyRecoveryFiltering.java b/src/test/java/com/rabbitmq/client/test/functional/TopologyRecoveryFiltering.java new file mode 100644 index 0000000000..8edd432a67 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/TopologyRecoveryFiltering.java @@ -0,0 +1,195 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.RecoverableConnection; +import com.rabbitmq.client.impl.recovery.RecordedBinding; +import com.rabbitmq.client.impl.recovery.RecordedConsumer; +import com.rabbitmq.client.impl.recovery.RecordedExchange; +import com.rabbitmq.client.impl.recovery.RecordedQueue; +import com.rabbitmq.client.impl.recovery.TopologyRecoveryFilter; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.client.test.TestUtils; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.rabbitmq.client.test.TestUtils.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +public class TopologyRecoveryFiltering extends BrokerTestCase { + + String[] exchangesToDelete = new String[] { + "recovered.exchange", "filtered.exchange", "topology.recovery.exchange" + }; + String[] queuesToDelete = new String[] { + "topology.recovery.queue.1", "topology.recovery.queue.2" + }; + Connection c; + + @Override + protected ConnectionFactory newConnectionFactory() { + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + connectionFactory.setTopologyRecoveryFilter(new SimpleTopologyRecoveryFilter()); + connectionFactory.setNetworkRecoveryInterval(1000); + return connectionFactory; + } + + @Override + protected void createResources() throws IOException, TimeoutException { + super.createResources(); + c = connectionFactory.newConnection(UUID.randomUUID().toString()); + deleteExchanges(exchangesToDelete); + deleteQueues(queuesToDelete); + } + + @Override + protected void releaseResources() throws IOException { + super.releaseResources(); + c.close(); + deleteExchanges(exchangesToDelete); + deleteQueues(queuesToDelete); + } + + @Test + public void topologyRecoveryFilteringExchangesAndQueues() throws Exception { + Channel ch = c.createChannel(); + ch.exchangeDeclare("recovered.exchange", "direct"); + ch.exchangeDeclare("filtered.exchange", "direct"); + ch.queueDeclare("recovered.queue", false, true, true, null); + ch.queueDeclare("filtered.queue", false, true, true, null); + + // to check whether the other connection recovers them or not + channel.exchangeDelete("recovered.exchange"); + channel.exchangeDelete("filtered.exchange"); + + closeAndWaitForRecovery((RecoverableConnection) c); + + assertTrue(exchangeExists("recovered.exchange", c)); + assertFalse(exchangeExists("filtered.exchange", c)); + + assertTrue(queueExists("recovered.queue", c)); + assertFalse(queueExists("filtered.queue", c)); + } + + @Test + public void topologyRecoveryFilteringBindings() throws Exception { + Channel ch = c.createChannel(); + + ch.exchangeDeclare("topology.recovery.exchange", "direct"); + ch.queueDeclare("topology.recovery.queue.1", false, false, false, null); + ch.queueDeclare("topology.recovery.queue.2", false, false, false, null); + ch.queueBind("topology.recovery.queue.1", "topology.recovery.exchange", "recovered.binding"); + ch.queueBind("topology.recovery.queue.2", "topology.recovery.exchange", "filtered.binding"); + + // to check whether the other connection recovers them or not + channel.queueUnbind("topology.recovery.queue.1", "topology.recovery.exchange", "recovered.binding"); + channel.queueUnbind("topology.recovery.queue.2", "topology.recovery.exchange", "filtered.binding"); + + closeAndWaitForRecovery((RecoverableConnection) c); + + assertTrue(sendAndConsumeMessage( + "topology.recovery.exchange", "recovered.binding", "topology.recovery.queue.1", c + ), "The message should have been received by now"); + assertFalse(sendAndConsumeMessage( + "topology.recovery.exchange", "filtered.binding", "topology.recovery.queue.2", c + ), "Binding shouldn't recover, no messages should have been received"); + } + + @Test + public void topologyRecoveryFilteringConsumers() throws Exception { + Channel ch = c.createChannel(); + + ch.exchangeDeclare("topology.recovery.exchange", "direct"); + ch.queueDeclare("topology.recovery.queue.1", false, false, false, null); + ch.queueDeclare("topology.recovery.queue.2", false, false, false, null); + ch.queueBind("topology.recovery.queue.1", "topology.recovery.exchange", "recovered.consumer"); + ch.queueBind("topology.recovery.queue.2", "topology.recovery.exchange", "filtered.consumer"); + + final AtomicInteger recoveredConsumerMessageCount = new AtomicInteger(0); + ch.basicConsume("topology.recovery.queue.1", true, "recovered.consumer", new DefaultConsumer(ch) { + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + recoveredConsumerMessageCount.incrementAndGet(); + } + }); + ch.basicPublish("topology.recovery.exchange", "recovered.consumer", null, "".getBytes()); + waitAtMost(Duration.ofSeconds(5), () -> recoveredConsumerMessageCount.get() == 1); + + final AtomicInteger filteredConsumerMessageCount = new AtomicInteger(0); + final CountDownLatch filteredConsumerLatch = new CountDownLatch(2); + ch.basicConsume("topology.recovery.queue.2", true, "filtered.consumer", new DefaultConsumer(ch) { + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + filteredConsumerMessageCount.incrementAndGet(); + filteredConsumerLatch.countDown(); + } + }); + ch.basicPublish("topology.recovery.exchange", "filtered.consumer", null, "".getBytes()); + waitAtMost(Duration.ofSeconds(5), () -> filteredConsumerMessageCount.get() == 1); + + closeAndWaitForRecovery((RecoverableConnection) c); + + int initialCount = recoveredConsumerMessageCount.get(); + ch.basicPublish("topology.recovery.exchange", "recovered.consumer", null, "".getBytes()); + waitAtMost(Duration.ofSeconds(5), () -> recoveredConsumerMessageCount.get() == initialCount + 1); + + ch.basicPublish("topology.recovery.exchange", "filtered.consumer", null, "".getBytes()); + assertFalse(filteredConsumerLatch.await(5, TimeUnit.SECONDS), + "Consumer shouldn't recover, no extra messages should have been received"); + } + + private static class SimpleTopologyRecoveryFilter implements TopologyRecoveryFilter { + + @Override + public boolean filterExchange(RecordedExchange recordedExchange) { + return !recordedExchange.getName().contains("filtered"); + } + + @Override + public boolean filterQueue(RecordedQueue recordedQueue) { + return !recordedQueue.getName().contains("filtered"); + } + + @Override + public boolean filterBinding(RecordedBinding recordedBinding) { + return !recordedBinding.getRoutingKey().contains("filtered"); + } + + @Override + public boolean filterConsumer(RecordedConsumer recordedConsumer) { + return !recordedConsumer.getConsumerTag().contains("filtered"); + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/functional/TopologyRecoveryRetry.java b/src/test/java/com/rabbitmq/client/test/functional/TopologyRecoveryRetry.java new file mode 100644 index 0000000000..f27cc03872 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/TopologyRecoveryRetry.java @@ -0,0 +1,205 @@ +// Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.Recoverable; +import com.rabbitmq.client.RecoveryListener; +import com.rabbitmq.client.AMQP.BasicProperties; +import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; +import com.rabbitmq.client.impl.recovery.RecordedBinding; +import com.rabbitmq.client.impl.recovery.RecordedConsumer; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.client.test.TestUtils; +import com.rabbitmq.tools.Host; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.util.HashMap; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import static com.rabbitmq.client.impl.recovery.TopologyRecoveryRetryLogic.RETRY_ON_QUEUE_NOT_FOUND_RETRY_HANDLER; +import static com.rabbitmq.client.test.TestUtils.closeAllConnectionsAndWaitForRecovery; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +public class TopologyRecoveryRetry extends BrokerTestCase { + + private volatile Consumer backoffConsumer; + + @BeforeEach + public void init() { + this.backoffConsumer = attempt -> { }; + } + + @Test + public void topologyRecoveryRetry() throws Exception { + int nbQueues = 200; + String prefix = "topology-recovery-retry-" + System.currentTimeMillis(); + for (int i = 0; i < nbQueues; i++) { + String queue = prefix + i; + channel.queueDeclare(queue, false, false, true, new HashMap<>()); + channel.queueBind(queue, "amq.direct", queue); + channel.queueBind(queue, "amq.direct", queue + "2"); + channel.basicConsume(queue, true, new DefaultConsumer(channel)); + } + + closeAllConnectionsAndWaitForRecovery(this.connection); + + assertTrue(channel.isOpen()); + } + + @Test + public void topologyRecoveryBindingFailure() throws Exception { + final String queue = "topology-recovery-retry-binding-failure" + System.currentTimeMillis(); + channel.queueDeclare(queue, false, false, true, new HashMap<>()); + channel.queueBind(queue, "amq.topic", "topic1"); + channel.queueBind(queue, "amq.topic", "topic2"); + final CountDownLatch messagesReceivedLatch = new CountDownLatch(2); + channel.basicConsume(queue, true, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) { + messagesReceivedLatch.countDown(); + } + }); + final CountDownLatch recoveryLatch = new CountDownLatch(1); + ((AutorecoveringConnection)connection).addRecoveryListener(new RecoveryListener() { + @Override + public void handleRecoveryStarted(Recoverable recoverable) { + // no-op + } + @Override + public void handleRecovery(Recoverable recoverable) { + recoveryLatch.countDown(); + } + }); + + // we want recovery to fail when recovering the 2nd binding + // give the 2nd recorded binding a bad queue name so it fails + final RecordedBinding binding2 = ((AutorecoveringConnection)connection).getRecordedBindings().get(1); + binding2.destination(UUID.randomUUID().toString()); + + // use the backoffConsumer to know that it has failed + // then delete the real queue & fix the recorded binding + // it should fail once more because queue is gone, and then succeed + final CountDownLatch backoffLatch = new CountDownLatch(1); + backoffConsumer = attempt -> { + if (attempt == 1) { + binding2.destination(queue); + try { + Host.rabbitmqctl("delete_queue " + queue); + Thread.sleep(2000); + } catch (Exception e) { + e.printStackTrace(); + } + } + backoffLatch.countDown(); + }; + + // close connection + Host.closeAllConnections(); + + // assert backoff was called + assertTrue(backoffLatch.await(90, TimeUnit.SECONDS)); + // wait for full recovery + assertTrue(recoveryLatch.await(90, TimeUnit.SECONDS)); + + // publish messages to verify both bindings were recovered + basicPublishVolatile("test1".getBytes(), "amq.topic", "topic1"); + basicPublishVolatile("test2".getBytes(), "amq.topic", "topic2"); + + assertTrue(messagesReceivedLatch.await(10, TimeUnit.SECONDS)); + } + + @Test + public void topologyRecoveryConsumerFailure() throws Exception { + final String queue = "topology-recovery-retry-consumer-failure" + System.currentTimeMillis(); + channel.queueDeclare(queue, false, false, true, new HashMap<>()); + channel.queueBind(queue, "amq.topic", "topic1"); + channel.queueBind(queue, "amq.topic", "topic2"); + final CountDownLatch messagesReceivedLatch = new CountDownLatch(2); + channel.basicConsume(queue, true, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) { + messagesReceivedLatch.countDown(); + } + }); + final CountDownLatch recoveryLatch = new CountDownLatch(1); + ((AutorecoveringConnection)connection).addRecoveryListener(new RecoveryListener() { + @Override + public void handleRecoveryStarted(Recoverable recoverable) { + // no-op + } + @Override + public void handleRecovery(Recoverable recoverable) { + recoveryLatch.countDown(); + } + }); + + // we want recovery to fail when recovering the consumer + // give the recorded consumer a bad queue name so it fails + final RecordedConsumer consumer = ((AutorecoveringConnection)connection).getRecordedConsumers().values().iterator().next(); + consumer.setQueue(UUID.randomUUID().toString()); + + // use the backoffConsumer to know that it has failed + // then delete the real queue & fix the recorded consumer + // it should fail once more because queue is gone, and then succeed + final CountDownLatch backoffLatch = new CountDownLatch(1); + backoffConsumer = attempt -> { + if (attempt == 1) { + consumer.setQueue(queue); + try { + Host.rabbitmqctl("delete_queue " + queue); + Thread.sleep(2000); + } catch (Exception e) { + e.printStackTrace(); + } + } + backoffLatch.countDown(); + }; + + // close connection + Host.closeAllConnections(); + + // assert backoff was called + assertTrue(backoffLatch.await(90, TimeUnit.SECONDS)); + // wait for full recovery + assertTrue(recoveryLatch.await(90, TimeUnit.SECONDS)); + + // publish messages to verify both bindings & consumer were recovered + basicPublishVolatile("test1".getBytes(), "amq.topic", "topic1"); + basicPublishVolatile("test2".getBytes(), "amq.topic", "topic2"); + + assertTrue(messagesReceivedLatch.await(10, TimeUnit.SECONDS)); + } + + @Override + protected ConnectionFactory newConnectionFactory() { + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + connectionFactory.setTopologyRecoveryRetryHandler(RETRY_ON_QUEUE_NOT_FOUND_RETRY_HANDLER + .backoffPolicy(attempt -> backoffConsumer.accept(attempt)).build()); + connectionFactory.setNetworkRecoveryInterval(1000); + return connectionFactory; + } +} diff --git a/test/src/com/rabbitmq/client/test/functional/Transactions.java b/src/test/java/com/rabbitmq/client/test/functional/Transactions.java similarity index 78% rename from test/src/com/rabbitmq/client/test/functional/Transactions.java rename to src/test/java/com/rabbitmq/client/test/functional/Transactions.java index 5a352844c4..9c67907ddc 100644 --- a/test/src/com/rabbitmq/client/test/functional/Transactions.java +++ b/src/test/java/com/rabbitmq/client/test/functional/Transactions.java @@ -1,27 +1,33 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.test.BrokerTestCase; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.IOException; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.test.BrokerTestCase; public class Transactions extends BrokerTestCase { @@ -108,7 +114,7 @@ private long[] publishSelectAndGet(int n) /* publishes are embargoed until commit */ - public void testCommitPublish() + @Test public void commitPublish() throws IOException { txSelect(); @@ -122,7 +128,7 @@ public void testCommitPublish() /* rollback rolls back publishes */ - public void testRollbackPublish() + @Test public void rollbackPublish() throws IOException { txSelect(); @@ -134,7 +140,7 @@ public void testRollbackPublish() /* closing a channel rolls back publishes */ - public void testRollbackPublishOnClose() + @Test public void rollbackPublishOnClose() throws IOException { txSelect(); @@ -147,7 +153,7 @@ public void testRollbackPublishOnClose() /* closing a channel requeues both ack'ed and un-ack'ed messages */ - public void testRequeueOnClose() + @Test public void requeueOnClose() throws IOException { basicPublish(); @@ -168,7 +174,7 @@ public void testRequeueOnClose() messages with committed acks are not requeued on channel close, messages that weren't ack'ed are requeued on close, but not before then. */ - public void testCommitAcks() + @Test public void commitAcks() throws IOException { basicPublish(); @@ -188,7 +194,7 @@ public void testCommitAcks() /* */ - public void testCommitAcksOutOfOrder() + @Test public void commitAcksOutOfOrder() throws IOException { long tags[] = publishSelectAndGet(4); @@ -203,7 +209,7 @@ public void testCommitAcksOutOfOrder() rollback rolls back acks and a rolled back ack can be re-issued */ - public void testRollbackAcksAndReAck() + @Test public void rollbackAcksAndReAck() throws IOException { basicPublish(); @@ -222,7 +228,7 @@ public void testRollbackAcksAndReAck() /* it is illegal to ack with an unknown delivery tag */ - public void testUnknownTagAck() + @Test public void unknownTagAck() throws IOException { basicPublish(); @@ -238,7 +244,7 @@ public void testUnknownTagAck() /* rollback does not requeue delivered ack'ed or un-ack'ed messages */ - public void testNoRequeueOnRollback() + @Test public void noRequeueOnRollback() throws IOException { basicPublish(); @@ -254,7 +260,7 @@ public void testNoRequeueOnRollback() /* auto-acks are not part of tx */ - public void testAutoAck() + @Test public void autoAck() throws IOException { basicPublish(); @@ -268,7 +274,7 @@ public void testAutoAck() /* "ack all", once committed, acks all delivered messages */ - public void testAckAll() + @Test public void ackAll() throws IOException { basicPublish(); @@ -283,7 +289,7 @@ public void testAckAll() assertNull(basicGet()); } - public void testNonTransactedCommit() + @Test public void nonTransactedCommit() throws IOException { try { @@ -294,7 +300,7 @@ public void testNonTransactedCommit() } } - public void testNonTransactedRollback() + @Test public void nonTransactedRollback() throws IOException { try { @@ -305,7 +311,7 @@ public void testNonTransactedRollback() } } - public void testRedeliverAckedUncommitted() + @Test public void redeliverAckedUncommitted() throws IOException { txSelect(); @@ -318,29 +324,29 @@ public void testRedeliverAckedUncommitted() basicAck(); channel.basicRecover(true); - assertNull("Acked uncommitted message redelivered", - basicGet(true)); + assertNull(basicGet(true), "Acked uncommitted message redelivered"); } - public void testCommitWithDeletedQueue() - throws IOException - { - txSelect(); - basicPublish(); - releaseResources(); - try { - txCommit(); - } catch (IOException e) { - closeConnection(); - openConnection(); - openChannel(); - fail("commit failed"); - } finally { - createResources(); // To allow teardown to function cleanly + @Test public void commitWithDeletedQueue() + throws IOException, TimeoutException { + if (beforeMessageContainers()) { + txSelect(); + basicPublish(); + releaseResources(); + try { + txCommit(); + } catch (IOException e) { + closeConnection(); + openConnection(); + openChannel(); + fail("commit failed"); + } finally { + createResources(); // To allow teardown to function cleanly + } } } - public void testShuffleAcksBeforeRollback() + @Test public void shuffleAcksBeforeRollback() throws IOException { long tags[] = publishSelectAndGet(3); @@ -433,37 +439,37 @@ public void commitAcksAndNacks(NackMethod method) assertNull(basicGet()); } - public void testCommitNacks() + @Test public void commitNacks() throws IOException { commitNacks(basicNack); } - public void testRollbackNacks() + @Test public void rollbackNacks() throws IOException { rollbackNacks(basicNack); } - public void testCommitAcksAndNacks() + @Test public void commitAcksAndNacks() throws IOException { commitAcksAndNacks(basicNack); } - public void testCommitRejects() + @Test public void commitRejects() throws IOException { commitNacks(basicReject); } - public void testRollbackRejects() + @Test public void rollbackRejects() throws IOException { rollbackNacks(basicReject); } - public void testCommitAcksAndRejects() + @Test public void commitAcksAndRejects() throws IOException { commitAcksAndNacks(basicReject); diff --git a/src/test/java/com/rabbitmq/client/test/functional/UnbindAutoDeleteExchange.java b/src/test/java/com/rabbitmq/client/test/functional/UnbindAutoDeleteExchange.java new file mode 100644 index 0000000000..61adda00b9 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/UnbindAutoDeleteExchange.java @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.test.BrokerTestCase; + +/** + * Test that unbinding from an auto-delete exchange causes the exchange to go + * away + */ +public class UnbindAutoDeleteExchange extends BrokerTestCase { + @Test public void unbind() throws IOException, InterruptedException { + String exchange = "myexchange"; + channel.exchangeDeclare(exchange, "fanout", false, true, null); + String queue = channel.queueDeclare().getQueue(); + channel.queueBind(queue, exchange, ""); + channel.queueUnbind(queue, exchange, ""); + + try { + channel.exchangeDeclarePassive(exchange); + fail("exchange should no longer be there"); + } + catch (IOException e) { + checkShutdownSignal(AMQP.NOT_FOUND, e); + } + } +} diff --git a/test/src/com/rabbitmq/client/test/functional/UnexpectedFrames.java b/src/test/java/com/rabbitmq/client/test/functional/UnexpectedFrames.java similarity index 63% rename from test/src/com/rabbitmq/client/test/functional/UnexpectedFrames.java rename to src/test/java/com/rabbitmq/client/test/functional/UnexpectedFrames.java index f5b2c613a1..4c9a51dec6 100644 --- a/test/src/com/rabbitmq/client/test/functional/UnexpectedFrames.java +++ b/src/test/java/com/rabbitmq/client/test/functional/UnexpectedFrames.java @@ -1,35 +1,32 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.functional; -import java.io.IOException; -import java.net.Socket; - import com.rabbitmq.client.AMQP; import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.DefaultSocketConfigurator; -import com.rabbitmq.client.impl.AMQConnection; -import com.rabbitmq.client.impl.Frame; -import com.rabbitmq.client.impl.FrameHandler; -import com.rabbitmq.client.impl.FrameHandlerFactory; -import com.rabbitmq.client.impl.SocketFrameHandler; +import com.rabbitmq.client.SocketConfigurators; +import com.rabbitmq.client.impl.*; +import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.client.test.TestUtils; +import org.junit.jupiter.api.Test; import javax.net.SocketFactory; +import java.io.IOException; +import java.net.Socket; /** * Test that the server correctly handles us when we send it bad frames @@ -37,7 +34,7 @@ public class UnexpectedFrames extends BrokerTestCase { private interface Confuser { - public Frame confuse(Frame frame) throws IOException; + Frame confuse(Frame frame) throws IOException; } private static class ConfusedFrameHandler extends SocketFrameHandler { @@ -71,14 +68,24 @@ public Frame confuse(Frame frame) { } private static class ConfusedConnectionFactory extends ConnectionFactory { + + public ConfusedConnectionFactory() { + super(); + if(TestUtils.USE_NIO) { + useNio(); + } else { + useBlockingIo(); + } + } + @Override protected FrameHandlerFactory createFrameHandlerFactory() { return new ConfusedFrameHandlerFactory(); } } - private static class ConfusedFrameHandlerFactory extends FrameHandlerFactory { + private static class ConfusedFrameHandlerFactory extends SocketFrameHandlerFactory { private ConfusedFrameHandlerFactory() { - super(1000, SocketFactory.getDefault(), new DefaultSocketConfigurator(), false); + super(1000, SocketFactory.getDefault(), SocketConfigurators.defaultConfigurator(), false); } @Override public FrameHandler create(Socket sock) throws IOException { @@ -91,10 +98,10 @@ public UnexpectedFrames() { connectionFactory = new ConfusedConnectionFactory(); } - public void testMissingHeader() throws IOException { + @Test public void missingHeader() throws IOException { expectUnexpectedFrameError(new Confuser() { public Frame confuse(Frame frame) { - if (frame.type == AMQP.FRAME_HEADER) { + if (frame.getType() == AMQP.FRAME_HEADER) { return null; } return frame; @@ -102,24 +109,24 @@ public Frame confuse(Frame frame) { }); } - public void testMissingMethod() throws IOException { + @Test public void missingMethod() throws IOException { expectUnexpectedFrameError(new Confuser() { public Frame confuse(Frame frame) { - if (frame.type == AMQP.FRAME_METHOD) { + if (frame.getType() == AMQP.FRAME_METHOD) { // We can't just skip the method as that will lead us to // send 0 bytes and hang waiting for a response. return new Frame(AMQP.FRAME_HEADER, - frame.channel, frame.getPayload()); + frame.getChannel(), frame.getPayload()); } return frame; } }); } - public void testMissingBody() throws IOException { + @Test public void missingBody() throws IOException { expectUnexpectedFrameError(new Confuser() { public Frame confuse(Frame frame) { - if (frame.type == AMQP.FRAME_BODY) { + if (frame.getType() == AMQP.FRAME_BODY) { return null; } return frame; @@ -127,13 +134,13 @@ public Frame confuse(Frame frame) { }); } - public void testWrongClassInHeader() throws IOException { + @Test public void wrongClassInHeader() throws IOException { expectUnexpectedFrameError(new Confuser() { public Frame confuse(Frame frame) { - if (frame.type == AMQP.FRAME_HEADER) { + if (frame.getType() == AMQP.FRAME_HEADER) { byte[] payload = frame.getPayload(); Frame confusedFrame = new Frame(AMQP.FRAME_HEADER, - frame.channel, payload); + frame.getChannel(), payload); // First two bytes = class ID, must match class ID from // method. payload[0] = 12; @@ -145,22 +152,22 @@ public Frame confuse(Frame frame) { }); } - public void testHeartbeatOnChannel() throws IOException { + @Test public void heartbeatOnChannel() throws IOException { expectUnexpectedFrameError(new Confuser() { public Frame confuse(Frame frame) { - if (frame.type == AMQP.FRAME_METHOD) { - return new Frame(AMQP.FRAME_HEARTBEAT, frame.channel); + if (frame.getType() == AMQP.FRAME_METHOD) { + return new Frame(AMQP.FRAME_HEARTBEAT, frame.getChannel()); } return frame; } }); } - public void testUnknownFrameType() throws IOException { + @Test public void unknownFrameType() throws IOException { expectError(AMQP.FRAME_ERROR, new Confuser() { public Frame confuse(Frame frame) { - if (frame.type == AMQP.FRAME_METHOD) { - return new Frame(0, frame.channel, + if (frame.getType() == AMQP.FRAME_METHOD) { + return new Frame(0, frame.getChannel(), "1234567890\0001234567890".getBytes()); } return frame; @@ -169,7 +176,7 @@ public Frame confuse(Frame frame) { } private void expectError(int error, Confuser confuser) throws IOException { - ((ConfusedFrameHandler)((AMQConnection)connection).getFrameHandler()). + ((ConfusedFrameHandler)((AutorecoveringConnection)connection).getDelegate().getFrameHandler()). confuser = confuser; //NB: the frame confuser relies on the encoding of the diff --git a/src/test/java/com/rabbitmq/client/test/functional/UserIDHeader.java b/src/test/java/com/rabbitmq/client/test/functional/UserIDHeader.java new file mode 100644 index 0000000000..e086fd5321 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/functional/UserIDHeader.java @@ -0,0 +1,68 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.functional; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.AlreadyClosedException; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.tools.Host; + +public class UserIDHeader extends BrokerTestCase { + private static final AMQP.BasicProperties GOOD = new AMQP.BasicProperties.Builder().userId("guest").build(); + private static final AMQP.BasicProperties BAD = new AMQP.BasicProperties.Builder().userId("not the guest, honest").build(); + + @Test public void validUserId() throws IOException { + publish(GOOD); + } + + @Test public void invalidUserId() { + try { + publish(BAD); + fail("Accepted publish with incorrect user ID"); + } catch (IOException e) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); + } catch (AlreadyClosedException e) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); + } + } + + @Test public void impersonatedUserId() throws IOException, TimeoutException { + Host.rabbitmqctl("set_user_tags guest administrator impersonator"); + try (Connection c = connectionFactory.newConnection()){ + publish(BAD, c.createChannel()); + } finally { + Host.rabbitmqctl("set_user_tags guest administrator"); + } + } + + private void publish(AMQP.BasicProperties properties) throws IOException { + publish(properties, this.channel); + } + + private void publish(AMQP.BasicProperties properties, Channel channel) throws IOException { + channel.basicPublish("amq.fanout", "", properties, "".getBytes()); + channel.queueDeclare(); // To flush the channel + } +} diff --git a/src/test/java/com/rabbitmq/client/test/server/AlternateExchangeEquivalence.java b/src/test/java/com/rabbitmq/client/test/server/AlternateExchangeEquivalence.java new file mode 100644 index 0000000000..7b716d943b --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/AlternateExchangeEquivalence.java @@ -0,0 +1,44 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.server; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.test.functional.ExchangeEquivalenceBase; + +public class AlternateExchangeEquivalence extends ExchangeEquivalenceBase { + static final Map args = new HashMap(); + { + args.put("alternate-exchange", "UME"); + } + + @Test public void alternateExchangeEquivalence() throws IOException { + channel.exchangeDeclare("alternate", "direct", false, false, args); + verifyEquivalent("alternate", "direct", false, false, args); + } + + @Test public void alternateExchangeNonEquivalence() throws IOException { + channel.exchangeDeclare("alternate", "direct", false, false, args); + Map altargs = new HashMap(); + altargs.put("alternate-exchange", "somewhere"); + verifyNotEquivalent("alternate", "direct", false, false, altargs); + } +} diff --git a/test/src/com/rabbitmq/client/test/server/BlockedConnection.java b/src/test/java/com/rabbitmq/client/test/server/BlockedConnection.java similarity index 52% rename from test/src/com/rabbitmq/client/test/server/BlockedConnection.java rename to src/test/java/com/rabbitmq/client/test/server/BlockedConnection.java index 0085e6d9db..32913606c6 100644 --- a/test/src/com/rabbitmq/client/test/server/BlockedConnection.java +++ b/src/test/java/com/rabbitmq/client/test/server/BlockedConnection.java @@ -1,33 +1,37 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.server; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import com.rabbitmq.client.test.TestUtils; +import org.junit.jupiter.api.Test; + import com.rabbitmq.client.BlockedListener; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.MessageProperties; import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.tools.Host; - -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; public class BlockedConnection extends BrokerTestCase { protected void releaseResources() throws IOException { @@ -37,28 +41,41 @@ protected void releaseResources() throws IOException { e.printStackTrace(); } } - public void testBlock() throws Exception { + // this test first opens a connection, then triggers + // and alarm and blocks + @Test public void testBlock() throws Exception { final CountDownLatch latch = new CountDownLatch(1); Connection connection = connection(latch); block(); publish(connection); - assertTrue(latch.await(10, TimeUnit.SECONDS)); + try { + assertTrue(latch.await(10, TimeUnit.SECONDS)); + } finally { + TestUtils.abort(connection); + } + } - public void testInitialBlock() throws Exception { + // this test first triggers an alarm, then opens a + // connection + @Test public void initialBlock() throws Exception { final CountDownLatch latch = new CountDownLatch(1); block(); Connection connection = connection(latch); publish(connection); - assertTrue(latch.await(10, TimeUnit.SECONDS)); + try { + assertTrue(latch.await(10, TimeUnit.SECONDS)); + } finally { + TestUtils.abort(connection); + } } - private Connection connection(final CountDownLatch latch) throws IOException { - ConnectionFactory factory = new ConnectionFactory(); + private Connection connection(final CountDownLatch latch) throws IOException, TimeoutException { + ConnectionFactory factory = TestUtils.connectionFactory(); Connection connection = factory.newConnection(); connection.addBlockedListener(new BlockedListener() { public void handleBlocked(String reason) throws IOException { diff --git a/test/src/com/rabbitmq/client/test/server/Bug19219Test.java b/src/test/java/com/rabbitmq/client/test/server/Bug19219Test.java similarity index 78% rename from test/src/com/rabbitmq/client/test/server/Bug19219Test.java rename to src/test/java/com/rabbitmq/client/test/server/Bug19219Test.java index b996fd867e..6b2da91a7e 100644 --- a/test/src/com/rabbitmq/client/test/server/Bug19219Test.java +++ b/src/test/java/com/rabbitmq/client/test/server/Bug19219Test.java @@ -1,26 +1,28 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.server; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeoutException; -import junit.framework.TestSuite; +import org.junit.jupiter.api.Test; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; @@ -55,12 +57,6 @@ public class Bug19219Test extends BrokerTestCase { private static final Semaphore init = new Semaphore(0); private static final CountDownLatch resume = new CountDownLatch(1); - public static TestSuite suite() { - TestSuite suite = new TestSuite("Bug19219"); - suite.addTestSuite(Bug19219Test.class); - return suite; - } - private static void publish(final Channel ch) throws IOException { ch.basicPublish("amq.fanout", "", @@ -68,7 +64,7 @@ private static void publish(final Channel ch) new byte[0]); } - public void testIt() throws IOException, InterruptedException { + @Test public void it() throws IOException, InterruptedException { final Consumer c = new DefaultConsumer(channel); @@ -89,6 +85,8 @@ public void run() { startPublisher(); } catch (IOException e) { } catch (InterruptedException e) { + } catch (TimeoutException e) { + e.printStackTrace(); } } }; @@ -112,7 +110,7 @@ public void run() { //notifications timing out. boolean success = false; try { - channel.close(); + channel.abort(); success = true; } catch (ShutdownSignalException e) { } finally { @@ -126,7 +124,7 @@ public void run() { } } - private void startPublisher() throws IOException, InterruptedException { + private void startPublisher() throws IOException, InterruptedException, TimeoutException { final Connection conn = connectionFactory.newConnection(); final Channel pubCh = conn.createChannel(); diff --git a/test/src/com/rabbitmq/client/test/server/ChannelLimitNegotiation.java b/src/test/java/com/rabbitmq/client/test/server/ChannelLimitNegotiation.java similarity index 63% rename from test/src/com/rabbitmq/client/test/server/ChannelLimitNegotiation.java rename to src/test/java/com/rabbitmq/client/test/server/ChannelLimitNegotiation.java index c590d05aa7..5c42fd89dc 100644 --- a/test/src/com/rabbitmq/client/test/server/ChannelLimitNegotiation.java +++ b/src/test/java/com/rabbitmq/client/test/server/ChannelLimitNegotiation.java @@ -1,5 +1,34 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.test.server; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.concurrent.Executors; + +import javax.net.SocketFactory; + +import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; +import com.rabbitmq.client.test.TestUtils; +import org.junit.jupiter.api.Test; + import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; @@ -7,21 +36,18 @@ import com.rabbitmq.client.ShutdownSignalException; import com.rabbitmq.client.impl.AMQConnection; import com.rabbitmq.client.impl.ChannelN; -import com.rabbitmq.client.impl.SocketFrameHandler; import com.rabbitmq.client.impl.ConsumerWorkService; +import com.rabbitmq.client.impl.SocketFrameHandler; import com.rabbitmq.client.test.BrokerTestCase; import com.rabbitmq.tools.Host; -import javax.net.SocketFactory; -import java.io.IOException; -import java.util.concurrent.Executors; - public class ChannelLimitNegotiation extends BrokerTestCase { + class SpecialConnection extends AMQConnection { private final int channelMax; public SpecialConnection(int channelMax) throws Exception { - this(new ConnectionFactory(), channelMax); + this(TestUtils.connectionFactory(), channelMax); } private SpecialConnection(ConnectionFactory factory, int channelMax) throws Exception { @@ -38,16 +64,17 @@ protected int negotiateChannelMax(int requestedChannelMax, int serverMax) { } } - public void testChannelMaxLowerThanServerMinimum() throws Exception { + @Test public void channelMaxLowerThanServerMinimum() throws Exception { int n = 64; - ConnectionFactory cf = new ConnectionFactory(); + ConnectionFactory cf = TestUtils.connectionFactory(); cf.setRequestedChannelMax(n); - Connection conn = cf.newConnection(); - assertEquals(n, conn.getChannelMax()); + try (Connection conn = cf.newConnection()) { + assertEquals(n, conn.getChannelMax()); + } } - public void testChannelMaxGreaterThanServerValue() throws Exception { + @Test public void channelMaxGreaterThanServerValue() throws Exception { try { Host.rabbitmqctl("eval 'application:set_env(rabbit, channel_max, 2048).'"); @@ -63,13 +90,14 @@ public void testChannelMaxGreaterThanServerValue() throws Exception { } } - public void testOpeningTooManyChannels() throws Exception { + @Test public void openingTooManyChannels() throws Exception { int n = 48; + Connection conn = null; try { Host.rabbitmqctl("eval 'application:set_env(rabbit, channel_max, " + n + ").'"); - ConnectionFactory cf = new ConnectionFactory(); - Connection conn = cf.newConnection(); + ConnectionFactory cf = TestUtils.connectionFactory(); + conn = cf.newConnection(); assertEquals(n, conn.getChannelMax()); for (int i = 1; i <= n; i++) { @@ -79,7 +107,7 @@ public void testOpeningTooManyChannels() throws Exception { assertNull(conn.createChannel(n + 1)); // Construct a channel directly - final ChannelN ch = new ChannelN((AMQConnection) conn, n + 1, + final ChannelN ch = new ChannelN(((AutorecoveringConnection) conn).getDelegate(), n + 1, new ConsumerWorkService(Executors.newSingleThreadExecutor(), Executors.defaultThreadFactory(), ConnectionFactory.DEFAULT_SHUTDOWN_TIMEOUT)); conn.addShutdownListener(new ShutdownListener() { @@ -93,6 +121,7 @@ public void shutdownCompleted(ShutdownSignalException cause) { } catch (IOException e) { checkShutdownSignal(530, e); } finally { + TestUtils.abort(conn); Host.rabbitmqctl("eval 'application:set_env(rabbit, channel_max, 0).'"); } } diff --git a/test/src/com/rabbitmq/client/test/server/DeadLetterExchangeDurable.java b/src/test/java/com/rabbitmq/client/test/server/DeadLetterExchangeDurable.java similarity index 66% rename from test/src/com/rabbitmq/client/test/server/DeadLetterExchangeDurable.java rename to src/test/java/com/rabbitmq/client/test/server/DeadLetterExchangeDurable.java index 58fc7de989..c22f6eaf58 100644 --- a/test/src/com/rabbitmq/client/test/server/DeadLetterExchangeDurable.java +++ b/src/test/java/com/rabbitmq/client/test/server/DeadLetterExchangeDurable.java @@ -1,14 +1,33 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.test.server; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.client.test.functional.DeadLetterExchange; -import com.rabbitmq.tools.Host; +import static org.junit.jupiter.api.Assertions.assertNull; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.MessageProperties; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.client.test.functional.DeadLetterExchange; +import com.rabbitmq.tools.Host; + public class DeadLetterExchangeDurable extends BrokerTestCase { @Override protected void createResources() throws IOException { @@ -30,18 +49,15 @@ protected void releaseResources() throws IOException { channel.queueDelete(DeadLetterExchange.TEST_QUEUE_NAME); } - public void testDeadLetterQueueTTLExpiredWhileDown() throws Exception { - // This test is nonsensical (and often breaks) in HA mode. - if (HATests.HA_TESTS_RUNNING) return; - + @Test public void deadLetterQueueTTLExpiredWhileDown() throws Exception { for(int x = 0; x < DeadLetterExchange.MSG_COUNT; x++) { channel.basicPublish("amq.direct", "test", MessageProperties.MINIMAL_PERSISTENT_BASIC, "test message".getBytes()); } closeConnection(); - Host.invokeMakeTarget("stop-app"); + Host.stopRabbitOnNode(); Thread.sleep(5000); - Host.invokeMakeTarget("start-app"); + Host.startRabbitOnNode(); openConnection(); openChannel(); diff --git a/test/src/com/rabbitmq/client/test/server/DurableBindingLifecycle.java b/src/test/java/com/rabbitmq/client/test/server/DurableBindingLifecycle.java similarity index 55% rename from test/src/com/rabbitmq/client/test/server/DurableBindingLifecycle.java rename to src/test/java/com/rabbitmq/client/test/server/DurableBindingLifecycle.java index c650775b7b..dedd6eff83 100644 --- a/test/src/com/rabbitmq/client/test/server/DurableBindingLifecycle.java +++ b/src/test/java/com/rabbitmq/client/test/server/DurableBindingLifecycle.java @@ -1,29 +1,33 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.server; -import com.rabbitmq.client.GetResponse; +import static com.rabbitmq.client.test.TestUtils.waitAtMost; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNull; -import com.rabbitmq.client.test.functional.BindingLifecycleBase; +import java.io.IOException; +import java.util.concurrent.TimeoutException; -import com.rabbitmq.tools.Host; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; -import java.io.IOException; +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.test.functional.BindingLifecycleBase; /** * This tests whether bindings are created and nuked properly. @@ -36,7 +40,7 @@ */ public class DurableBindingLifecycle extends BindingLifecycleBase { @Override - protected void restart() throws IOException { + protected void restart() throws IOException, TimeoutException { if (clusteredConnection != null) { clusteredConnection.abort(); clusteredConnection = null; @@ -44,21 +48,22 @@ protected void restart() throws IOException { alternateConnection = null; alternateChannel = null; - Host.invokeMakeTarget("restart-secondary-node"); + stopSecondary(); + startSecondary(); } restartPrimary(); } - private void restartPrimary() throws IOException { - tearDown(); + private void restartPrimary() throws IOException, TimeoutException { + tearDown(this.testInfo); bareRestart(); - setUp(); + setUp(this.testInfo); } /** * Tests whether durable bindings are correctly recovered. */ - public void testDurableBindingRecovery() throws IOException { + @Test public void durableBindingRecovery() throws IOException, TimeoutException { declareDurableTopicExchange(X); declareAndBindDurableQueue(Q, X, K); @@ -68,7 +73,16 @@ public void testDurableBindingRecovery() throws IOException { basicPublishVolatile(X, K); } - assertDelivered(Q, N); + AtomicInteger receivedCount = new AtomicInteger(0); + waitAtMost(() -> { + GetResponse r; + r = basicGet(Q); + if (r != null) { + assertThat(r.getEnvelope().isRedeliver()).isFalse(); + receivedCount.incrementAndGet(); + } + return receivedCount.get() == N; + }); deleteQueue(Q); deleteExchange(X); @@ -76,9 +90,9 @@ public void testDurableBindingRecovery() throws IOException { /** * This tests whether the bindings attached to a durable exchange - * are correctly blown away when the exhange is nuked. + * are correctly blown away when the exchange is nuked. * - * This complements a unit test for testing non-durable exhanges. + * This complements a unit test for testing non-durable exchanges. * In that case, an exchange is deleted and you expect any * bindings hanging to it to be deleted as well. To verify this, * the exchange is deleted and then recreated. @@ -91,7 +105,7 @@ public void testDurableBindingRecovery() throws IOException { * main difference is that the broker has to be restarted to * verify that the durable routes have been turfed. */ - public void testDurableBindingsDeletion() throws IOException { + @Test public void durableBindingsDeletion() throws IOException, TimeoutException { declareDurableTopicExchange(X); declareAndBindDurableQueue(Q, X, K); @@ -106,7 +120,7 @@ public void testDurableBindingsDeletion() throws IOException { } GetResponse response = channel.basicGet(Q, true); - assertNull("The initial response SHOULD BE null", response); + assertNull(response, "The initial response SHOULD BE null"); deleteQueue(Q); deleteExchange(X); @@ -120,15 +134,14 @@ public void testDurableBindingsDeletion() throws IOException { * The idea is to create a durable queue, nuke the server and then * publish a message to it using the queue name as a routing key */ - public void testDefaultBindingRecovery() throws IOException { + @Test public void defaultBindingRecovery() throws IOException, TimeoutException { declareDurableQueue(Q); restart(); basicPublishVolatile("", Q); - GetResponse response = channel.basicGet(Q, true); - assertNotNull("The initial response SHOULD NOT be null", response); + waitAtMost(() -> channel.basicGet(Q, true) != null); deleteQueue(Q); } @@ -138,7 +151,7 @@ public void testDefaultBindingRecovery() throws IOException { * queue and the durable queue is on a cluster node that restarts, * we do not lose the binding. See bug 24009. */ - public void testTransientExchangeDurableQueue() throws IOException { + @Test public void transientExchangeDurableQueue() throws IOException, TimeoutException { // This test depends on the second node in the cluster to keep // the transient X alive if (clusteredConnection != null) { @@ -149,7 +162,15 @@ public void testTransientExchangeDurableQueue() throws IOException { restartPrimary(); basicPublishVolatile("transientX", ""); - assertDelivered("durableQ", 1); + waitAtMost(() -> { + GetResponse response = channel.basicGet("durableQ", true); + if (response == null) { + return false; + } else { + assertThat(response.getEnvelope().isRedeliver()).isFalse(); + return true; + } + }); deleteExchange("transientX"); deleteQueue("durableQ"); diff --git a/src/test/java/com/rabbitmq/client/test/server/EffectVisibilityCrossNodeTest.java b/src/test/java/com/rabbitmq/client/test/server/EffectVisibilityCrossNodeTest.java new file mode 100644 index 0000000000..fa8404a3b7 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/EffectVisibilityCrossNodeTest.java @@ -0,0 +1,89 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.server; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.util.concurrent.*; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.test.functional.ClusteredTestBase; + +/** + * From bug 19844 - we want to be sure that publish vs everything else can't + * happen out of order + */ +public class EffectVisibilityCrossNodeTest extends ClusteredTestBase { + private final String[] queues = new String[QUEUES]; + + ExecutorService executorService; + + @Override + protected void createResources() throws IOException { + for (int i = 0; i < queues.length ; i++) { + queues[i] = alternateChannel.queueDeclare("", false, false, true, null).getQueue(); + alternateChannel.queueBind(queues[i], "amq.fanout", ""); + } + executorService = Executors.newSingleThreadExecutor(); + } + + @Override + protected void releaseResources() throws IOException { + executorService.shutdownNow(); + for (int i = 0; i < queues.length ; i++) { + alternateChannel.queueDelete(queues[i]); + } + } + + private static final int QUEUES = 5; + private static final int BATCHES = 100; + private static final int MESSAGES_PER_BATCH = 5; + + private static final byte[] msg = "".getBytes(); + + @Test public void effectVisibility() throws Exception { + // the test bulk is asynchronous because this test has a history of hanging + Future task = + executorService.submit( + () -> { + for (int i = 0; i < BATCHES; i++) { + Thread.sleep(10); // to avoid flow control for the connection + for (int j = 0; j < MESSAGES_PER_BATCH; j++) { + channel.basicPublish("amq.fanout", "", null, msg); + } + for (int j = 0; j < queues.length; j++) { + String queue = queues[j]; + long timeout = 10 * 1000; + long waited = 0; + int purged = 0; + while (waited < timeout) { + purged += channel.queuePurge(queue).getMessageCount(); + if (purged == MESSAGES_PER_BATCH) { + break; + } + Thread.sleep(10); + waited += 10; + } + assertEquals(MESSAGES_PER_BATCH, purged, "Queue " + queue + " should have been purged after 10 seconds"); + } + } + return null; + }); + task.get(1, TimeUnit.MINUTES); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/server/ExclusiveQueueDurability.java b/src/test/java/com/rabbitmq/client/test/server/ExclusiveQueueDurability.java new file mode 100644 index 0000000000..8e3badabbc --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/ExclusiveQueueDurability.java @@ -0,0 +1,90 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.server; + +import static com.rabbitmq.client.test.TestUtils.safeDelete; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.test.BrokerTestCase; + +/** + * This tests whether exclusive, durable queues are deleted when appropriate + * (following the scenarios given in bug 20578). + */ +public class ExclusiveQueueDurability extends BrokerTestCase { + + @Override + protected boolean isAutomaticRecoveryEnabled() { + // With automatic recovery enabled, queue can be re-created when launching the test suite + // (because FunctionalTests are launched independently and as part of the HATests) + // This then makes this test fail. + return false; + } + + void verifyQueueMissing(Channel channel, String queueName) { + try { + channel.queueDeclare(queueName, false, false, false, null); + } catch (IOException ioe) { + checkShutdownSignal(AMQP.RESOURCE_LOCKED, ioe); + fail("Declaring the queue resulted in a channel exception, probably meaning that it already exists"); + } + } + + // 1) connection and queue are on same node, node restarts -> queue + // should no longer exist + @Test public void connectionQueueSameNode() throws Exception { + String queueName = generateQueueName(); + safeDelete(connection, queueName); + try { + channel.queueDeclare(queueName, true, true, false, null); + restartPrimaryAbruptly(); + verifyQueueMissing(channel, queueName); + } finally { + safeDelete(connection, queueName); + } + } + + private void restartPrimaryAbruptly() throws IOException, TimeoutException { + connection = null; + channel = null; + bareRestart(); + setUp(this.testInfo); + } + + /* + * The other scenarios: + * + * 2) connection and queue are on different nodes, queue's node restarts, + * connection is still alive -> queue should exist + * + * 3) connection and queue are on different nodes, queue's node restarts, + * connection has been terminated in the meantime -> queue should no longer + * exist + * + * There's no way to test these, as things stand; connections and queues are + * tied to nodes, so one can't engineer a situation in which a connection + * and its exclusive queue are on different nodes. + */ + +} diff --git a/test/src/com/rabbitmq/client/test/server/Firehose.java b/src/test/java/com/rabbitmq/client/test/server/Firehose.java similarity index 65% rename from test/src/com/rabbitmq/client/test/server/Firehose.java rename to src/test/java/com/rabbitmq/client/test/server/Firehose.java index d7cd1c4dda..10643c441b 100644 --- a/test/src/com/rabbitmq/client/test/server/Firehose.java +++ b/src/test/java/com/rabbitmq/client/test/server/Firehose.java @@ -1,19 +1,40 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.test.server; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.tools.Host; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.tools.Host; public class Firehose extends BrokerTestCase { private String q; private String firehose; @Override - protected void createResources() throws IOException { + protected void createResources() throws IOException, TimeoutException { super.createResources(); channel.exchangeDeclare("test", "fanout", false, true, null); q = channel.queueDeclare().getQueue(); @@ -22,7 +43,7 @@ protected void createResources() throws IOException { channel.queueBind(firehose, "amq.rabbitmq.trace", "#"); } - public void testFirehose() throws IOException { + @Test public void firehose() throws IOException { publishGet("not traced"); enable(); GetResponse msg = publishGet("traced"); diff --git a/src/test/java/com/rabbitmq/client/test/server/HaTestSuite.java b/src/test/java/com/rabbitmq/client/test/server/HaTestSuite.java new file mode 100644 index 0000000000..1d2f21af99 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/HaTestSuite.java @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.server; + +import com.rabbitmq.client.test.functional.FunctionalTestSuite; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectClasses({ + FunctionalTestSuite.class, + ServerTestSuite.class, + LastHaTestSuite.class, +}) +public class HaTestSuite { + +} diff --git a/src/test/java/com/rabbitmq/client/test/server/LastHaTestSuite.java b/src/test/java/com/rabbitmq/client/test/server/LastHaTestSuite.java new file mode 100644 index 0000000000..39a11ad632 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/LastHaTestSuite.java @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.server; + +import com.rabbitmq.client.test.server.LastHaTestSuite.DummyTest; +import org.junit.jupiter.api.Test; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +/** + * Marker test suite to signal the end of the HA test suite. + */ +@Suite +@SelectClasses(DummyTest.class) +public class LastHaTestSuite { + + static class DummyTest { + @Test + void noOp() { + + } + } + +} diff --git a/test/src/com/rabbitmq/client/test/server/LoopbackUsers.java b/src/test/java/com/rabbitmq/client/test/server/LoopbackUsers.java similarity index 52% rename from test/src/com/rabbitmq/client/test/server/LoopbackUsers.java rename to src/test/java/com/rabbitmq/client/test/server/LoopbackUsers.java index 55ec3c62e0..e81de3703e 100644 --- a/test/src/com/rabbitmq/client/test/server/LoopbackUsers.java +++ b/src/test/java/com/rabbitmq/client/test/server/LoopbackUsers.java @@ -1,9 +1,21 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + package com.rabbitmq.client.test.server; -import com.rabbitmq.client.AuthenticationFailureException; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.tools.Host; -import junit.framework.TestCase; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.net.Inet4Address; @@ -11,47 +23,70 @@ import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; +import java.util.concurrent.TimeoutException; -public class LoopbackUsers extends TestCase { - @Override - protected void setUp() throws IOException { +import com.rabbitmq.client.test.TestUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AuthenticationFailureException; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.tools.Host; + +public class LoopbackUsers { + + @BeforeEach public void setUp() throws IOException { Host.rabbitmqctl("add_user test test"); Host.rabbitmqctl("set_permissions test '.*' '.*' '.*'"); } - @Override - protected void tearDown() throws IOException { + @AfterEach public void tearDown() throws IOException { Host.rabbitmqctl("delete_user test"); } - public void testLoopback() throws IOException { - String addr = findRealIPAddress().getHostAddress(); - assertGuestFail(addr); - Host.rabbitmqctl("eval 'application:set_env(rabbit, loopback_users, []).'"); - assertGuestSucceed(addr); - Host.rabbitmqctl("eval 'application:set_env(rabbit, loopback_users, [<<\"guest\">>]).'"); - assertGuestFail(addr); + @Test public void loopback() throws IOException, TimeoutException { + if (!Host.isOnDocker()) { + String addr = findRealIPAddress().getHostAddress(); + String initialValue = getLoopbackUsers(); + try { + setLoopbackUsers("[]"); + assertGuestSucceed(addr); + setLoopbackUsers("[<<\"guest\">>]"); + assertGuestFail(addr); + } finally { + setLoopbackUsers(initialValue); + } + } + } + + private static String getLoopbackUsers() throws IOException { + return Host.rabbitmqctl("eval '{ok, V} = application:get_env(rabbit, loopback_users), V.'").output(); + } + + private static void setLoopbackUsers(String value) throws IOException { + Host.rabbitmqctl(String.format("eval 'application:set_env(rabbit, loopback_users, %s).'", value)); } - private void assertGuestSucceed(String addr) throws IOException { + private void assertGuestSucceed(String addr) throws IOException, TimeoutException { succeedConnect("guest", addr); succeedConnect("guest", "localhost"); succeedConnect("test", addr); succeedConnect("test", "localhost"); } - private void assertGuestFail(String addr) throws IOException { + private void assertGuestFail(String addr) throws IOException, TimeoutException { failConnect("guest", addr); succeedConnect("guest", "localhost"); succeedConnect("test", addr); succeedConnect("test", "localhost"); } - private void succeedConnect(String name, String addr) throws IOException { + private void succeedConnect(String name, String addr) throws IOException, TimeoutException { getFactory(name, addr).newConnection().close(); } - private void failConnect(String name, String addr) throws IOException { + private void failConnect(String name, String addr) throws IOException, TimeoutException { try { getFactory(name, addr).newConnection(); fail(); @@ -62,7 +97,7 @@ private void failConnect(String name, String addr) throws IOException { } private ConnectionFactory getFactory(String name, String addr) { - ConnectionFactory factory = new ConnectionFactory(); + ConnectionFactory factory = TestUtils.connectionFactory(); factory.setUsername(name); factory.setPassword(name); factory.setHost(addr); diff --git a/test/src/com/rabbitmq/client/test/server/MemoryAlarms.java b/src/test/java/com/rabbitmq/client/test/server/MemoryAlarms.java similarity index 62% rename from test/src/com/rabbitmq/client/test/server/MemoryAlarms.java rename to src/test/java/com/rabbitmq/client/test/server/MemoryAlarms.java index cc04174c2f..c2e0217a5a 100644 --- a/test/src/com/rabbitmq/client/test/server/MemoryAlarms.java +++ b/src/test/java/com/rabbitmq/client/test/server/MemoryAlarms.java @@ -1,28 +1,35 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.server; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.tools.Host; +import org.junit.jupiter.api.TestInfo; public class MemoryAlarms extends BrokerTestCase { @@ -31,18 +38,21 @@ public class MemoryAlarms extends BrokerTestCase { private Connection connection2; private Channel channel2; + @BeforeEach @Override - protected void setUp() throws IOException { + public void setUp(TestInfo info) throws IOException, TimeoutException { connectionFactory.setRequestedHeartbeat(1); - super.setUp(); + super.setUp(info); if (connection2 == null) { connection2 = connectionFactory.newConnection(); } channel2 = connection2.createChannel(); } + @AfterEach @Override - protected void tearDown() throws IOException { + public void tearDown(TestInfo info) throws IOException, TimeoutException { + clearAllResourceAlarms(); if (channel2 != null) { channel2.abort(); channel2 = null; @@ -51,7 +61,7 @@ protected void tearDown() throws IOException { connection2.abort(); connection2 = null; } - super.tearDown(); + super.tearDown(info); connectionFactory.setRequestedHeartbeat(0); } @@ -62,16 +72,10 @@ protected void createResources() throws IOException { @Override protected void releaseResources() throws IOException { - try { - clearAllResourceAlarms(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - channel.queueDelete(Q); - } + channel.queueDelete(Q); } - public void testFlowControl() throws IOException, InterruptedException { + @Test public void flowControl() throws IOException, InterruptedException { basicPublishVolatile(Q); setResourceAlarm("memory"); // non-publish actions only after an alarm should be fine @@ -93,7 +97,7 @@ public void testFlowControl() throws IOException, InterruptedException { } - public void testOverlappingAlarmsFlowControl() throws IOException, InterruptedException { + @Test public void overlappingAlarmsFlowControl() throws IOException, InterruptedException { QueueingConsumer c = new QueueingConsumer(channel); String consumerTag = channel.basicConsume(Q, true, c); diff --git a/src/test/java/com/rabbitmq/client/test/server/MessageRecovery.java b/src/test/java/com/rabbitmq/client/test/server/MessageRecovery.java new file mode 100644 index 0000000000..c5a4a47431 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/MessageRecovery.java @@ -0,0 +1,54 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.server; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.MessageProperties; +import com.rabbitmq.client.test.ConfirmBase; +import org.junit.jupiter.api.Test; + +public class MessageRecovery extends ConfirmBase +{ + + private final static String Q = "recovery-test"; + private final static String Q2 = "recovery-test-ha-check"; + + @Test public void messageRecovery() + throws Exception + { + channel.queueDelete(Q); + channel.queueDelete(Q2); + channel.confirmSelect(); + channel.queueDeclare(Q, true, false, false, null); + channel.basicPublish("", Q, false, false, + MessageProperties.PERSISTENT_BASIC, + "nop".getBytes()); + waitForConfirms(); + + channel.queueDeclare(Q2, false, false, false, null); + + restart(); + + assertDelivered(Q, 1, false); + channel.queueDelete(Q); + channel.queueDelete(Q2); + } + +} diff --git a/test/src/com/rabbitmq/client/test/server/Permissions.java b/src/test/java/com/rabbitmq/client/test/server/Permissions.java similarity index 80% rename from test/src/com/rabbitmq/client/test/server/Permissions.java rename to src/test/java/com/rabbitmq/client/test/server/Permissions.java index 5e32c36d22..44d0bb18de 100644 --- a/test/src/com/rabbitmq/client/test/server/Permissions.java +++ b/src/test/java/com/rabbitmq/client/test/server/Permissions.java @@ -1,36 +1,38 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.client.test.server; -import com.rabbitmq.client.AlreadyClosedException; -import com.rabbitmq.client.AuthenticationFailureException; +import com.rabbitmq.client.*; +import com.rabbitmq.client.impl.AMQChannel; +import com.rabbitmq.client.impl.recovery.AutorecoveringChannel; import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.client.test.TestUtils; +import com.rabbitmq.tools.Host; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import java.io.IOException; -import java.util.Map; import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.TestInfo; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.QueueingConsumer; -import com.rabbitmq.client.impl.AMQChannel; -import com.rabbitmq.tools.Host; +import static org.junit.jupiter.api.Assertions.*; public class Permissions extends BrokerTestCase { @@ -39,25 +41,27 @@ public class Permissions extends BrokerTestCase public Permissions() { - ConnectionFactory factory = new ConnectionFactory(); + ConnectionFactory factory = TestUtils.connectionFactory(); factory.setUsername("test"); factory.setPassword("test"); factory.setVirtualHost("/test"); connectionFactory = factory; } - protected void setUp() - throws IOException - { + @BeforeEach + @Override + public void setUp(TestInfo info) + throws IOException, TimeoutException { deleteRestrictedAccount(); addRestrictedAccount(); - super.setUp(); + super.setUp(info); } - protected void tearDown() - throws IOException - { - super.tearDown(); + @AfterEach + @Override + public void tearDown(TestInfo info) + throws IOException, TimeoutException { + super.tearDown(info); deleteRestrictedAccount(); } @@ -82,9 +86,8 @@ protected void deleteRestrictedAccount() } protected void createResources() - throws IOException - { - ConnectionFactory factory = new ConnectionFactory(); + throws IOException, TimeoutException { + ConnectionFactory factory = TestUtils.connectionFactory(); factory.setUsername("testadmin"); factory.setPassword("test"); factory.setVirtualHost("/test"); @@ -117,9 +120,8 @@ protected void withNames(WithName action) action.with("none"); } - public void testAuth() - { - ConnectionFactory unAuthFactory = new ConnectionFactory(); + @Test public void auth() throws TimeoutException { + ConnectionFactory unAuthFactory = TestUtils.connectionFactory(); unAuthFactory.setUsername("test"); unAuthFactory.setPassword("tset"); @@ -129,12 +131,11 @@ public void testAuth() } catch (IOException e) { assertTrue(e instanceof AuthenticationFailureException); String msg = e.getMessage(); - assertTrue("Exception message should contain 'auth'", - msg.toLowerCase().contains("auth")); + assertTrue(msg.toLowerCase().contains("auth"), "Exception message should contain 'auth'"); } } - public void testExchangeConfiguration() + @Test public void exchangeConfiguration() throws IOException { runConfigureTest(new WithName() { @@ -147,7 +148,7 @@ public void with(String name) throws IOException { }}); } - public void testQueueConfiguration() + @Test public void queueConfiguration() throws IOException { runConfigureTest(new WithName() { @@ -160,7 +161,7 @@ public void with(String name) throws IOException { }}); } - public void testPassiveDeclaration() throws IOException { + @Test public void passiveDeclaration() throws IOException { runTest(true, true, true, true, new WithName() { public void with(String name) throws IOException { channel.exchangeDeclarePassive(name); @@ -171,7 +172,7 @@ public void with(String name) throws IOException { }}); } - public void testBinding() + @Test public void binding() throws IOException { runTest(false, true, false, false, new WithName() { @@ -184,7 +185,7 @@ public void with(String name) throws IOException { }}); } - public void testPublish() + @Test public void publish() throws IOException { runTest(false, true, false, false, new WithName() { @@ -196,7 +197,7 @@ public void with(String name) throws IOException { }}); } - public void testGet() + @Test public void get() throws IOException { runTest(false, false, true, false, new WithName() { @@ -205,7 +206,7 @@ public void with(String name) throws IOException { }}); } - public void testConsume() + @Test public void consume() throws IOException { runTest(false, false, true, false, new WithName() { @@ -214,19 +215,19 @@ public void with(String name) throws IOException { }}); } - public void testPurge() + @Test public void purge() throws IOException { runTest(false, false, true, false, new WithName() { public void with(String name) throws IOException { - ((AMQChannel)channel) - .exnWrappingRpc(new AMQP.Queue.Purge.Builder() - .queue(name) + AMQChannel channelDelegate = (AMQChannel) ((AutorecoveringChannel)channel).getDelegate(); + channelDelegate.exnWrappingRpc(new AMQP.Queue.Purge.Builder() + .queue(name) .build()); }}); } - public void testAltExchConfiguration() + @Test public void altExchConfiguration() throws IOException { runTest(false, false, false, false, @@ -237,7 +238,7 @@ public void testAltExchConfiguration() createAltExchConfigTest("configure-and-read-me")); } - public void testDLXConfiguration() + @Test public void dLXConfiguration() throws IOException { runTest(false, false, false, false, @@ -248,7 +249,7 @@ public void testDLXConfiguration() createDLXConfigTest("configure-and-read-me")); } - public void testNoAccess() + @Test public void noAccess() throws IOException, InterruptedException { Host.rabbitmqctl("set_permissions -p /test test \"\" \"\" \"\""); @@ -371,13 +372,13 @@ protected void runTest(boolean exp, String name, WithName test) String msg = "'" + name + "' -> " + exp; try { test.with(name); - assertTrue(msg, exp); + assertTrue(exp, msg); } catch (IOException e) { - assertFalse(msg, exp); + assertFalse(exp, msg); checkShutdownSignal(AMQP.ACCESS_REFUSED, e); openChannel(); } catch (AlreadyClosedException e) { - assertFalse(msg, exp); + assertFalse(exp, msg); checkShutdownSignal(AMQP.ACCESS_REFUSED, e); openChannel(); } diff --git a/src/test/java/com/rabbitmq/client/test/server/PersistenceGuarantees.java b/src/test/java/com/rabbitmq/client/test/server/PersistenceGuarantees.java new file mode 100644 index 0000000000..8a085d598e --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/PersistenceGuarantees.java @@ -0,0 +1,72 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.server; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; + +import com.rabbitmq.client.impl.nio.NioParams; +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.MessageProperties; +import com.rabbitmq.client.test.BrokerTestCase; + +public class PersistenceGuarantees extends BrokerTestCase { + private static final int COUNT = 10000; + private String queue; + + @Override + protected NioParams nioParams() { + NioParams nioParams = super.nioParams(); + // may need a higher enqueuing timeout on slow environments + return nioParams + .setWriteEnqueuingTimeoutInMs(nioParams.getWriteEnqueuingTimeoutInMs() * 3) + .setWriteQueueCapacity(nioParams.getWriteQueueCapacity() * 2); + } + + protected void declareQueue() throws IOException { + queue = channel.queueDeclare("", true, false, false, null).getQueue(); + } + + @Test public void txPersistence() throws Exception { + declareQueue(); + channel.txSelect(); + publish(); + channel.txCommit(); + restart(); + assertPersisted(); + } + + @Test public void confirmPersistence() throws Exception { + declareQueue(); + channel.confirmSelect(); + publish(); + channel.waitForConfirms(); + restart(); + assertPersisted(); + } + + private void assertPersisted() throws IOException { + assertEquals(COUNT, channel.queueDelete(queue).getMessageCount()); + } + + private void publish() throws IOException { + for (int i = 0; i < COUNT; i++) { + channel.basicPublish("", queue, false, false, MessageProperties.PERSISTENT_BASIC, "".getBytes()); + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/server/PriorityQueues.java b/src/test/java/com/rabbitmq/client/test/server/PriorityQueues.java new file mode 100644 index 0000000000..6d034cf26e --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/PriorityQueues.java @@ -0,0 +1,124 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.server; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.test.BrokerTestCase; + +public class PriorityQueues extends BrokerTestCase { + @Test public void prioritisingBasics() throws IOException, TimeoutException, InterruptedException { + String q = "with-3-priorities"; + int n = 3; + channel.queueDeclare(q, true, false, false, argsWithPriorities(n)); + publishWithPriorities(q, n); + + List xs = prioritiesOfEnqueuedMessages(q, n); + assertEquals(Integer.valueOf(3), xs.get(0)); + assertEquals(Integer.valueOf(2), xs.get(1)); + assertEquals(Integer.valueOf(1), xs.get(2)); + + channel.queueDelete(q); + } + + @Test public void negativeMaxPriority() throws IOException, TimeoutException, InterruptedException { + String q = "with-minus-10-priorities"; + int n = -10; + try { + channel.queueDeclare(q, true, false, false, argsWithPriorities(n)); + fail("Negative priority, the queue creation should have failed"); + } catch (IOException ioe) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, ioe); + } + } + + @Test public void excessiveMaxPriority() throws IOException, TimeoutException, InterruptedException { + String q = "with-260-priorities"; + int n = 260; + try { + channel.queueDeclare(q, true, false, false, argsWithPriorities(n)); + fail("Priority too high (> 255), the queue creation should have failed"); + } catch (IOException ioe) { + checkShutdownSignal(AMQP.PRECONDITION_FAILED, ioe); + } + } + + @Test public void maxAllowedPriority() throws IOException, TimeoutException, InterruptedException { + String q = "with-255-priorities"; + int n = 255; + channel.queueDeclare(q, true, false, false, argsWithPriorities(n)); + publishWithPriorities(q, n); + + List xs = prioritiesOfEnqueuedMessages(q, n); + assertEquals(Integer.valueOf(255), xs.get(0)); + assertEquals(Integer.valueOf(254), xs.get(1)); + assertEquals(Integer.valueOf(253), xs.get(2)); + + channel.queueDelete(q); + } + + private List prioritiesOfEnqueuedMessages(String q, int n) throws InterruptedException, IOException { + final List xs = new ArrayList(); + final CountDownLatch latch = new CountDownLatch(n); + + channel.basicConsume(q, true, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, + AMQP.BasicProperties properties, byte[] body) throws IOException { + xs.add(properties.getPriority()); + latch.countDown(); + } + }); + + latch.await(5, TimeUnit.SECONDS); + return xs; + } + + private void publishWithPriorities(String q, int n) throws IOException, TimeoutException, InterruptedException { + channel.confirmSelect(); + for (int i = 1; i <= n; i++) { + channel.basicPublish("", q, propsWithPriority(i), "msg".getBytes("UTF-8")); + } + channel.waitForConfirms(500); + } + + private AMQP.BasicProperties propsWithPriority(int n) { + return (new AMQP.BasicProperties.Builder()) + .priority(n) + .build(); + } + + private Map argsWithPriorities(int n) { + Map m = new HashMap(); + m.put("x-max-priority", n); + return m; + } +} diff --git a/src/test/java/com/rabbitmq/client/test/server/ServerTestSuite.java b/src/test/java/com/rabbitmq/client/test/server/ServerTestSuite.java new file mode 100644 index 0000000000..746aabbe9a --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/ServerTestSuite.java @@ -0,0 +1,44 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.server; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectClasses({ + Permissions.class, + DurableBindingLifecycle.class, + DeadLetterExchangeDurable.class, + EffectVisibilityCrossNodeTest.class, + ExclusiveQueueDurability.class, + AlternateExchangeEquivalence.class, + MemoryAlarms.class, + MessageRecovery.class, + Firehose.class, + PersistenceGuarantees.class, + Shutdown.class, + BlockedConnection.class, + ChannelLimitNegotiation.class, + LoopbackUsers.class, + XDeathHeaderGrowth.class, + PriorityQueues.class, + TopicPermissions.class +}) +public class ServerTestSuite { + +} diff --git a/src/test/java/com/rabbitmq/client/test/server/Shutdown.java b/src/test/java/com/rabbitmq/client/test/server/Shutdown.java new file mode 100644 index 0000000000..76294511a0 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/Shutdown.java @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.server; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.test.BrokerTestCase; + +public class Shutdown extends BrokerTestCase { + + @Test public void errorOnShutdown() throws Exception { + bareRestart(); + expectError(AMQP.CONNECTION_FORCED); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/server/TopicPermissions.java b/src/test/java/com/rabbitmq/client/test/server/TopicPermissions.java new file mode 100644 index 0000000000..b2c8f42ff6 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/TopicPermissions.java @@ -0,0 +1,146 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.server; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.AlreadyClosedException; +import com.rabbitmq.client.BuiltinExchangeType; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.tools.Host; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeoutException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.fail; + +public class TopicPermissions extends BrokerTestCase { + + private static final Logger LOGGER = LoggerFactory.getLogger(TopicPermissions.class); + + String protectedTopic = "protected.topic"; + String notProtectedTopic = "not.protected.topic"; + String noneTopicExchange = "not.a.topic"; + + @Override + protected boolean shouldRun() throws IOException { + LOGGER.debug("Checking if test should run"); + return Host.isRabbitMqCtlCommandAvailable("set_topic_permissions"); + } + + @Override + protected void createResources() throws IOException, TimeoutException { + LOGGER.debug("Creating AMQP resources"); + channel.exchangeDeclare(protectedTopic, BuiltinExchangeType.TOPIC); + channel.exchangeDeclare(notProtectedTopic, BuiltinExchangeType.TOPIC); + channel.exchangeDeclare(noneTopicExchange, BuiltinExchangeType.DIRECT); + + LOGGER.debug("Setting permissions"); + Host.rabbitmqctl("set_topic_permissions -p / guest " + protectedTopic + " \"^{username}\" \"^{username}\""); + Host.rabbitmqctl("set_topic_permissions -p / guest " + noneTopicExchange + " \"^{username}\" \"^{username}\""); + } + + @Override + protected void releaseResources() throws IOException { + LOGGER.debug("Deleting AMQP resources"); + channel.exchangeDelete(protectedTopic); + channel.exchangeDelete(notProtectedTopic); + channel.exchangeDelete(noneTopicExchange); + + LOGGER.debug("Clearing permissions"); + Host.rabbitmqctl("clear_topic_permissions -p / guest"); + } + + @Test + public void topicPermissions() throws IOException { + assertAccessOk("Routing key matches on protected topic, should pass", () -> { + channel.basicPublish(protectedTopic, "guest.b.c", null, "content".getBytes()); + channel.basicQos(0); + return null; + }); + assertAccessRefused("Routing key does not match on protected topic, should not pass", () -> { + channel.basicPublish(protectedTopic, "b.c", null, "content".getBytes()); + channel.basicQos(0); + return null; + }); + assertAccessOk("Message sent on not-protected exchange, should pass", () -> { + channel.basicPublish(notProtectedTopic, "guest.b.c", null, "content".getBytes()); + channel.basicQos(0); + return null; + }); + assertAccessOk("Routing key does not match on protected exchange, but not a topic, should pass", () -> { + channel.basicPublish(noneTopicExchange, "b.c", null, "content".getBytes()); + channel.basicQos(0); + return null; + }); + assertAccessOk("Binding/unbinding on protected exchange with matching routing key, should pass", () -> { + String queue = channel.queueDeclare().getQueue(); + channel.queueBind(queue, protectedTopic, "guest.y.z"); + channel.basicQos(0); + channel.queueUnbind(queue, protectedTopic, "guest.y.z"); + channel.basicQos(0); + return null; + }); + assertAccessRefused("Binding/unbinding on protected exchange with none-matching routing key, should not pass", () -> { + String queue = channel.queueDeclare().getQueue(); + channel.queueBind(queue, protectedTopic, "y.z"); + channel.basicQos(0); + channel.queueUnbind(queue, protectedTopic, "y.z"); + channel.basicQos(0); + return null; + }); + assertAccessOk("Binding/unbinding on not-protected exchange with none-matching routing key, should pass", () -> { + String queue = channel.queueDeclare().getQueue(); + channel.queueBind(queue, notProtectedTopic, "y.z"); + channel.basicQos(0); + channel.queueUnbind(queue, notProtectedTopic, "y.z"); + channel.basicQos(0); + return null; + }); + } + + void assertAccessOk(String description, Callable action) { + LOGGER.debug("Running '" + description + "'"); + try { + action.call(); + } catch(Exception e) { + fail(description + " (" + e.getMessage()+")"); + } finally { + LOGGER.debug("'" + description + "' done"); + } + } + + void assertAccessRefused(String description, Callable action) throws IOException { + LOGGER.debug("Running '" + description + "'"); + try { + action.call(); + fail(description); + } catch (IOException e) { + checkShutdownSignal(AMQP.ACCESS_REFUSED, e); + openChannel(); + } catch (AlreadyClosedException e) { + checkShutdownSignal(AMQP.ACCESS_REFUSED, e); + openChannel(); + } catch(Exception e) { + fail("Unexpected exception: " + e.getMessage()); + } finally { + LOGGER.debug("'" + description + "' done"); + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/server/XDeathHeaderGrowth.java b/src/test/java/com/rabbitmq/client/test/server/XDeathHeaderGrowth.java new file mode 100644 index 0000000000..b61c7bd077 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/server/XDeathHeaderGrowth.java @@ -0,0 +1,256 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.server; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import org.junit.jupiter.api.Test; + +import com.rabbitmq.client.AMQP; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.DefaultConsumer; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.test.BrokerTestCase; + +class RejectingConsumer extends DefaultConsumer { + private CountDownLatch latch; + private Map headers; + private AtomicLong counter; + + public RejectingConsumer(Channel channel, CountDownLatch latch) { + super(channel); + this.latch = latch; + this.counter = new AtomicLong(latch.getCount()); + } + + @Override + public void handleDelivery(String consumerTag, Envelope envelope, + AMQP.BasicProperties properties, byte[] body) + throws IOException { + + if(this.latch.getCount() > 0) { + this.getChannel().basicReject(envelope.getDeliveryTag(), false); + } else { + if(this.getChannel().isOpen()) { + this.getChannel().basicAck(envelope.getDeliveryTag(), false); + } + } + if(this.counter.decrementAndGet() == 0) { + // get headers only when the message has been redelivered + // the expected number of times. + // it looks like the message can be redelivered because + // of the reject when handleDelivery isn't done yet or + // before the latch releases the main thread. There's then + // an additional delivery and the checks (transiently) fail. + this.headers = properties.getHeaders(); + } + latch.countDown(); + } + + public Map getHeaders() { + return headers; + } +} + +public class XDeathHeaderGrowth extends BrokerTestCase { + @SuppressWarnings("unchecked") + @Test public void boundedXDeathHeaderGrowth() throws IOException, InterruptedException { + final String x1 = "issues.rabbitmq-server-78.fanout1"; + declareTransientFanoutExchange(x1); + final String x2 = "issues.rabbitmq-server-78.fanout2"; + declareTransientFanoutExchange(x2); + final String x3 = "issues.rabbitmq-server-78.fanout3"; + declareTransientFanoutExchange(x3); + + final String q1 = "issues.rabbitmq-server-78.queue1"; + declareTransientQueue(q1, argumentsForDeadLetteringTo(x1)); + + final String q2 = "issues.rabbitmq-server-78.queue2"; + declareTransientQueue(q2, argumentsForDeadLetteringTo(x2)); + this.channel.queueBind(q2, x1, ""); + + final String q3 = "issues.rabbitmq-server-78.queue3"; + declareTransientQueue(q3, argumentsForDeadLetteringTo(x3)); + this.channel.queueBind(q3, x2, ""); + + final String qz = "issues.rabbitmq-server-78.destination"; + declareTransientQueue(qz, argumentsForDeadLetteringWithoutTtlTo(x3)); + this.channel.queueBind(qz, x3, ""); + + CountDownLatch latch = new CountDownLatch(10); + RejectingConsumer cons = new RejectingConsumer(this.channel, latch); + this.channel.basicConsume(qz, cons); + + this.channel.basicPublish("", q1, null, "msg".getBytes()); + assertTrue(latch.await(5, TimeUnit.SECONDS)); + List> events = (List>)cons.getHeaders().get("x-death"); + assertEquals(4, events.size()); + + List qs = new ArrayList(); + for (Map evt : events) { + qs.add(evt.get("queue").toString()); + } + Collections.sort(qs); + assertEquals(Arrays.asList(qz, q1, q2, q3), qs); + List cs = new ArrayList(); + for (Map evt : events) { + cs.add((Long)evt.get("count")); + } + Collections.sort(cs); + assertEquals(Arrays.asList(1L, 1L, 1L, 9L), cs); + + cleanUpExchanges(x1, x2, x3); + cleanUpQueues(q1, q2, q3, qz); + } + + private void cleanUpExchanges(String... xs) throws IOException { + for(String x : xs) { + this.channel.exchangeDelete(x); + } + } + private void cleanUpQueues(String... qs) throws IOException { + for(String q : qs) { + this.channel.queueDelete(q); + } + } + + @SuppressWarnings("unchecked") + @Test public void handlingOfXDeathHeadersFromEarlierVersions() throws IOException, InterruptedException { + final String x1 = "issues.rabbitmq-server-152.fanout1"; + declareTransientFanoutExchange(x1); + final String x2 = "issues.rabbitmq-server-152.fanout2"; + declareTransientFanoutExchange(x2); + + final String q1 = "issues.rabbitmq-server-152.queue1"; + declareTransientQueue(q1, argumentsForDeadLetteringTo(x1)); + + final String q2 = "issues.rabbitmq-server-152.queue2"; + declareTransientQueue(q2, argumentsForDeadLetteringTo(x2)); + this.channel.queueBind(q2, x1, ""); + + final String qz = "issues.rabbitmq-server-152.destination"; + declareTransientQueue(qz, argumentsForDeadLetteringWithoutTtlTo(x2)); + this.channel.queueBind(qz, x2, ""); + + CountDownLatch latch = new CountDownLatch(10); + RejectingConsumer cons = new RejectingConsumer(this.channel, latch); + this.channel.basicConsume(qz, cons); + + final AMQP.BasicProperties.Builder bldr = new AMQP.BasicProperties.Builder(); + AMQP.BasicProperties props = bldr.headers( + propsWithLegacyXDeathsInHeaders("issues.rabbitmq-server-152.queue97", + "issues.rabbitmq-server-152.queue97", + "issues.rabbitmq-server-152.queue97", + "issues.rabbitmq-server-152.queue98", + "issues.rabbitmq-server-152.queue99")).build(); + this.channel.basicPublish("", q1, props, "msg".getBytes()); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + List> events = (List>)cons.getHeaders().get("x-death"); + if (beforeMessageContainers()) { + assertEquals(6, events.size()); + } else { + assertEquals(3, events.size()); + } + + List qs = new ArrayList(); + for (Map evt : events) { + qs.add(evt.get("queue").toString()); + } + Collections.sort(qs); + if (beforeMessageContainers()) { + assertEquals(Arrays.asList(qz, q1, q2, + "issues.rabbitmq-server-152.queue97", + "issues.rabbitmq-server-152.queue98", + "issues.rabbitmq-server-152.queue99"), qs); + } else { + assertEquals(Arrays.asList(qz, q1, q2), qs); + } + + List cs = new ArrayList(); + for (Map evt : events) { + cs.add((Long)evt.get("count")); + } + Collections.sort(cs); + if (beforeMessageContainers()) { + assertEquals(Arrays.asList(1L, 1L, 4L, 4L, 9L, 12L), cs); + } else { + assertEquals(Arrays.asList(1L, 1L, 9L), cs); + } + + cleanUpExchanges(x1, x2); + cleanUpQueues(q1, q2, qz, + "issues.rabbitmq-server-152.queue97", + "issues.rabbitmq-server-152.queue98", + "issues.rabbitmq-server-152.queue99"); + } + + private Map propsWithLegacyXDeathsInHeaders(String... qs) { + Map m = new HashMap(); + List> xDeaths = new ArrayList>(); + for(String q : qs) { + xDeaths.add(newXDeath(q)); + xDeaths.add(newXDeath(q)); + xDeaths.add(newXDeath(q)); + xDeaths.add(newXDeath(q)); + } + + m.put("x-death", xDeaths); + return m; + } + + private Map newXDeath(String q) { + Map m = new HashMap(); + m.put("reason", "expired"); + m.put("queue", q); + m.put("exchange", "issues.rabbitmq-server-152.fanout0"); + m.put("routing-keys", Arrays.asList("routing-key-1", "routing-key-2")); + m.put("random", UUID.randomUUID().toString()); + + return m; + } + + private Map argumentsForDeadLetteringTo(String dlx) { + return argumentsForDeadLetteringTo(dlx, 1); + } + + private Map argumentsForDeadLetteringWithoutTtlTo(String dlx) { + return argumentsForDeadLetteringTo(dlx, -1); + } + + private Map argumentsForDeadLetteringTo(String dlx, int ttl) { + Map m = new HashMap(); + m.put("x-dead-letter-exchange", dlx); + m.put("x-dead-letter-routing-key", "some-routing-key"); + if(ttl > 0) { + m.put("x-message-ttl", ttl); + } + return m; + } +} diff --git a/src/test/java/com/rabbitmq/client/test/ssl/BadVerifiedConnection.java b/src/test/java/com/rabbitmq/client/test/ssl/BadVerifiedConnection.java new file mode 100644 index 0000000000..4c0646fe9e --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ssl/BadVerifiedConnection.java @@ -0,0 +1,56 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.ssl; + +import org.junit.jupiter.api.Test; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import java.io.IOException; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Test for bug 19356 - SSL Support in rabbitmq + * + */ +@EnabledForJreRange(min = JRE.JAVA_11) +public class BadVerifiedConnection extends UnverifiedConnection { + + public void openConnection() + throws IOException, TimeoutException { + try { + SSLContext c = TlsTestUtils.badVerifiedSslContext(); + connectionFactory.useSslProtocol(c); + } catch (Exception ex) { + throw new IOException(ex); + } + + try { + connection = connectionFactory.newConnection(); + fail(); + } catch (SSLHandshakeException ignored) { + } catch (IOException e) { + fail(); + } + } + + public void openChannel() {} + @Test public void sSL() {} +} diff --git a/src/test/java/com/rabbitmq/client/test/ssl/ConnectionFactoryDefaultTlsVersion.java b/src/test/java/com/rabbitmq/client/test/ssl/ConnectionFactoryDefaultTlsVersion.java new file mode 100644 index 0000000000..5928b02301 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ssl/ConnectionFactoryDefaultTlsVersion.java @@ -0,0 +1,42 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.ssl; + +import com.rabbitmq.client.ConnectionFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ConnectionFactoryDefaultTlsVersion { + + @Test public void defaultTlsVersionJdk16ShouldTakeFallback() { + String [] supportedProtocols = {"SSLv2Hello", "SSLv3", "TLSv1"}; + String tlsProtocol = ConnectionFactory.computeDefaultTlsProtocol(supportedProtocols); + Assertions.assertEquals("TLSv1",tlsProtocol); + } + + @Test public void defaultTlsVersionJdk17ShouldTakePrefered() { + String [] supportedProtocols = {"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"}; + String tlsProtocol = ConnectionFactory.computeDefaultTlsProtocol(supportedProtocols); + Assertions.assertEquals("TLSv1.2",tlsProtocol); + } + + @Test public void defaultTlsVersionJdk18ShouldTakePrefered() { + String [] supportedProtocols = {"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"}; + String tlsProtocol = ConnectionFactory.computeDefaultTlsProtocol(supportedProtocols); + Assertions.assertEquals("TLSv1.2",tlsProtocol); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/ssl/HostnameVerification.java b/src/test/java/com/rabbitmq/client/test/ssl/HostnameVerification.java new file mode 100644 index 0000000000..dce1565ae2 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ssl/HostnameVerification.java @@ -0,0 +1,94 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.ssl; + +import com.rabbitmq.client.Address; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.test.TestUtils; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import java.util.function.Consumer; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnabledForJreRange(min = JRE.JAVA_11) +public class HostnameVerification { + + static SSLContext sslContext; + + public static Object[] data() { + return new Object[] { + blockingIo(enableHostnameVerification()), + nio(enableHostnameVerification()), + }; + } + + private static Consumer blockingIo(final Consumer customizer) { + return connectionFactory -> { + connectionFactory.useBlockingIo(); + customizer.accept(connectionFactory); + }; + } + + private static Consumer nio(final Consumer customizer) { + return connectionFactory -> { + connectionFactory.useNio(); + customizer.accept(connectionFactory); + }; + } + + private static Consumer enableHostnameVerification() { + return connectionFactory -> connectionFactory.enableHostnameVerification(); + } + + @BeforeAll + public static void initCrypto() throws Exception { + sslContext = TlsTestUtils.verifiedSslContext(); + } + + @ParameterizedTest + @MethodSource("data") + public void hostnameVerificationFailsBecauseCertificateNotIssuedForLoopbackInterface(Consumer customizer) throws Exception { + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + connectionFactory.useSslProtocol(sslContext); + customizer.accept(connectionFactory); + Assertions.assertThatThrownBy(() -> connectionFactory.newConnection( + () -> singletonList(new Address("127.0.0.1", ConnectionFactory.DEFAULT_AMQP_OVER_SSL_PORT)))) + .isInstanceOf(SSLHandshakeException.class) + .as("The server certificate isn't issued for 127.0.0.1, the TLS handshake should have failed"); + } + + @ParameterizedTest + @MethodSource("data") + public void hostnameVerificationSucceeds(Consumer customizer) throws Exception { + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + connectionFactory.useSslProtocol(sslContext); + customizer.accept(connectionFactory); + try (Connection conn = connectionFactory.newConnection( + () -> singletonList(new Address("localhost", ConnectionFactory.DEFAULT_AMQP_OVER_SSL_PORT)))) { + assertTrue(conn.isOpen()); + } + } +} diff --git a/src/test/java/com/rabbitmq/client/test/ssl/NioTlsUnverifiedConnection.java b/src/test/java/com/rabbitmq/client/test/ssl/NioTlsUnverifiedConnection.java new file mode 100644 index 0000000000..15c026c0f4 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ssl/NioTlsUnverifiedConnection.java @@ -0,0 +1,218 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.ssl; + +import static com.rabbitmq.client.test.TestUtils.basicGetBasicConsume; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.TrustEverythingTrustManager; +import com.rabbitmq.client.impl.nio.NioParams; +import com.rabbitmq.client.test.BrokerTestCase; +import com.rabbitmq.client.test.TestUtils; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketTimeoutException; +import java.util.Collection; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import org.junit.jupiter.api.Test; +import org.netcrusher.core.reactor.NioReactor; +import org.netcrusher.tcp.TcpCrusher; +import org.netcrusher.tcp.TcpCrusherBuilder; +import org.slf4j.LoggerFactory; + +/** + * + */ +public class NioTlsUnverifiedConnection extends BrokerTestCase { + + public static final String QUEUE = "tls.nio.queue"; + + public void openConnection() + throws IOException, TimeoutException { + try { + connectionFactory.useSslProtocol(); + connectionFactory.useNio(); + } catch (Exception ex) { + throw new IOException(ex.toString()); + } + + int attempt = 0; + while(attempt < 3) { + try { + connection = connectionFactory.newConnection(); + break; + } catch(Exception e) { + LoggerFactory.getLogger(getClass()).warn("Error when opening TLS connection"); + attempt++; + } + } + if(connection == null) { + fail("Couldn't open TLS connection after 3 attempts"); + } + + } + + @Override + protected void releaseResources() throws IOException { + channel.queueDelete(QUEUE); + } + + @Test + public void connectionGetConsume() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + basicGetBasicConsume(connection, QUEUE, latch, 100 * 1000); + boolean messagesReceived = latch.await(5, TimeUnit.SECONDS); + assertTrue(messagesReceived, "Message has not been received"); + } + + @Test + public void connectionGetConsumeProtocols() throws Exception { + Collection availableProtocols = TlsTestUtils.availableTlsProtocols(); + Collection protocols = Stream.of("TLSv1.2", "TLSv1.3") + .filter(p -> availableProtocols.contains(p)) + .collect(Collectors.toList()); + for (String protocol : protocols) { + SSLContext sslContext = SSLContext.getInstance(protocol); + sslContext.init(null, new TrustManager[] {new TrustEverythingTrustManager()}, null); + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.useSslProtocol(sslContext); + cf.useNio(); + AtomicReference engine = new AtomicReference<>(); + cf.setNioParams(new NioParams() + .setSslEngineConfigurator(sslEngine -> engine.set(sslEngine))); + try (Connection c = cf.newConnection()) { + CountDownLatch latch = new CountDownLatch(1); + basicGetBasicConsume(c, QUEUE, latch, 100); + boolean messagesReceived = latch.await(5, TimeUnit.SECONDS); + assertTrue(messagesReceived, "Message has not been received"); + assertThat(engine.get()).isNotNull(); + assertThat(engine.get().getEnabledProtocols()).contains(protocol); + } + } + } + + @Test public void socketChannelConfigurator() throws Exception { + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.useNio(); + connectionFactory.useSslProtocol(); + NioParams nioParams = new NioParams(); + final AtomicBoolean sslEngineHasBeenCalled = new AtomicBoolean(false); + nioParams.setSslEngineConfigurator(sslEngine -> sslEngineHasBeenCalled.set(true)); + + connectionFactory.setNioParams(nioParams); + + Connection connection = null; + try { + connection = connectionFactory.newConnection(); + assertTrue(sslEngineHasBeenCalled.get(), "The SSL engine configurator should have called"); + } finally { + if (connection != null) { + connection.close(); + } + } + } + + @Test public void messageSize() throws Exception { + int[] sizes = new int[]{100, 1000, 10 * 1000, 1 * 1000 * 1000, 5 * 1000 * 1000}; + for (int size : sizes) { + sendAndVerifyMessage(size); + } + } + + // The purpose of this test is to put some stress on client TLS layer (SslEngineByteBufferInputStream to be specific) + // in an attempt to trigger condition described in https://github.com/rabbitmq/rabbitmq-java-client/issues/317 + // Unfortunately it is not guaranteed to be reproducible + @Test public void largeMessagesTlsTraffic() throws Exception { + for (int i = 0; i < 50; i++) { + sendAndVerifyMessage(76390); + } + } + + @Test + public void connectionShouldEnforceConnectionTimeout() throws Exception { + int amqpPort = 5671; // assumes RabbitMQ server running on localhost; + int amqpProxyPort = TestUtils.randomNetworkPort(); + + int connectionTimeout = 3_000; + int handshakeTimeout = 1_000; + + try (NioReactor reactor = new NioReactor(); + TcpCrusher tcpProxy = + TcpCrusherBuilder.builder() + .withReactor(reactor) + .withBindAddress(new InetSocketAddress(amqpProxyPort)) + .withConnectAddress("localhost", amqpPort) + .build()) { + + tcpProxy.open(); + tcpProxy.freeze(); + + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + factory.setPort(amqpProxyPort); + + factory.useSslProtocol(); + factory.useNio(); + + factory.setConnectionTimeout(connectionTimeout); + factory.setHandshakeTimeout(handshakeTimeout); + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + try { + CountDownLatch latch = new CountDownLatch(1); + executorService.submit( + () -> { + try { + factory.newConnection(); + latch.countDown(); + } catch (SocketTimeoutException e) { + latch.countDown(); + } catch (Exception e) { + // not supposed to happen + } + }); + + boolean connectionCreatedTimedOut = latch.await(10, TimeUnit.SECONDS); + assertThat(connectionCreatedTimedOut).isTrue(); + + } finally { + executorService.shutdownNow(); + } + } + } + + private void sendAndVerifyMessage(int size) throws Exception { + CountDownLatch latch = new CountDownLatch(1); + boolean messageReceived = basicGetBasicConsume(connection, QUEUE, latch, size); + assertTrue(messageReceived, "Message has not been received"); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/ssl/SslTestSuite.java b/src/test/java/com/rabbitmq/client/test/ssl/SslTestSuite.java new file mode 100644 index 0000000000..f4aba114e4 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ssl/SslTestSuite.java @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.client.test.ssl; + +import com.rabbitmq.client.test.SslContextFactoryTest; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectClasses({ + UnverifiedConnection.class, + VerifiedConnection.class, + BadVerifiedConnection.class, + ConnectionFactoryDefaultTlsVersion.class, + NioTlsUnverifiedConnection.class, + HostnameVerification.class, + TlsConnectionLogging.class, + SslContextFactoryTest.class +}) +public class SslTestSuite { + +} diff --git a/src/test/java/com/rabbitmq/client/test/ssl/TlsConnectionLogging.java b/src/test/java/com/rabbitmq/client/test/ssl/TlsConnectionLogging.java new file mode 100644 index 0000000000..0369927d4b --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ssl/TlsConnectionLogging.java @@ -0,0 +1,97 @@ +// Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.ssl; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.impl.TlsUtils; +import com.rabbitmq.client.impl.nio.NioParams; +import com.rabbitmq.client.test.TestUtils; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.net.ssl.*; +import java.security.cert.X509Certificate; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class TlsConnectionLogging { + + public static Object[] certificateInfoAreProperlyExtracted() { + return new Object[]{blockingIo(), nio()}; + } + + public static Function> blockingIo() { + return connectionFactory -> { + connectionFactory.useBlockingIo(); + AtomicReference socketCaptor = new AtomicReference<>(); + connectionFactory.setSocketConfigurator(socket -> socketCaptor.set((SSLSocket) socket)); + return () -> socketCaptor.get().getSession(); + }; + } + + public static Function> nio() { + return connectionFactory -> { + connectionFactory.useNio(); + AtomicReference sslEngineCaptor = new AtomicReference<>(); + connectionFactory.setNioParams(new NioParams() + .setSslEngineConfigurator(sslEngine -> sslEngineCaptor.set(sslEngine))); + return () -> sslEngineCaptor.get().getSession(); + }; + } + + @ParameterizedTest + @MethodSource + public void certificateInfoAreProperlyExtracted(Function> configurer) throws Exception { + SSLContext sslContext = TlsTestUtils.getSSLContext(); + sslContext.init(null, new TrustManager[]{new AlwaysTrustTrustManager()}, null); + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + connectionFactory.useSslProtocol(sslContext); + Supplier sslSessionSupplier = configurer.apply(connectionFactory); + try (Connection ignored = connectionFactory.newConnection()) { + SSLSession session = sslSessionSupplier.get(); + assertNotNull(session); + String info = TlsUtils.peerCertificateInfo(session.getPeerCertificates()[0], "some prefix"); + Assertions.assertThat(info).contains("some prefix") + .contains("CN=") + .contains("X.509 usage extensions") + .contains("KeyUsage"); + + } + } + + private static class AlwaysTrustTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java b/src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java new file mode 100644 index 0000000000..f85829c119 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java @@ -0,0 +1,147 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom +// Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.ssl; + +import com.rabbitmq.tools.Host; +import java.io.FileInputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +class TlsTestUtils { + + static final String[] PROTOCOLS = new String[] {"TLSv1.3", "TLSv1.2"}; + + private TlsTestUtils() {} + + static SSLContext badVerifiedSslContext() throws Exception { + return sslContext(trustManagerFactory(clientCertificate())); + } + + static SSLContext verifiedSslContext() throws Exception { + return sslContext(trustManagerFactory(caCertificate())); + } + + static SSLContext verifiedSslContext(CallableSupplier sslContextSupplier) + throws Exception { + return sslContext(sslContextSupplier, trustManagerFactory(caCertificate())); + } + + public static SSLContext getSSLContext() throws NoSuchAlgorithmException { + SSLContext c; + + // pick the first protocol available, preferring TLSv1.2, then TLSv1, + // falling back to SSLv3 if running on an ancient/crippled JDK + for (String proto : Arrays.asList("TLSv1.3", "TLSv1.2", "TLSv1", "SSLv3")) { + try { + c = SSLContext.getInstance(proto); + return c; + } catch (NoSuchAlgorithmException x) { + // keep trying + } + } + throw new NoSuchAlgorithmException(); + } + + static Collection availableTlsProtocols() { + try { + String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols(); + return Arrays.stream(protocols) + .filter(p -> p.toLowerCase().startsWith("tls")) + .collect(Collectors.toList()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + static SSLContext sslContext(TrustManagerFactory trustManagerFactory) throws Exception { + return sslContext(() -> SSLContext.getInstance(PROTOCOLS[0]), trustManagerFactory); + } + + static SSLContext sslContext( + CallableSupplier sslContextSupplier, TrustManagerFactory trustManagerFactory) + throws Exception { + SSLContext sslContext = sslContextSupplier.get(); + sslContext.init( + null, trustManagerFactory == null ? null : trustManagerFactory.getTrustManagers(), null); + return sslContext; + } + + static TrustManagerFactory trustManagerFactory(Certificate certificate) throws Exception { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setCertificateEntry("some-certificate", certificate); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + return trustManagerFactory; + } + + static X509Certificate caCertificate() throws Exception { + return loadCertificate(caCertificateFile()); + } + + static String caCertificateFile() { + return tlsArtefactPath( + System.getProperty("ca.certificate", "./rabbitmq-configuration/tls/ca_certificate.pem")); + } + + static X509Certificate clientCertificate() throws Exception { + return loadCertificate(clientCertificateFile()); + } + + static String clientCertificateFile() { + return tlsArtefactPath( + System.getProperty( + "client.certificate", + "./rabbitmq-configuration/tls/client_" + hostname() + "_certificate.pem")); + } + + static X509Certificate loadCertificate(String file) throws Exception { + try (FileInputStream inputStream = new FileInputStream(file)) { + CertificateFactory fact = CertificateFactory.getInstance("X.509"); + return (X509Certificate) fact.generateCertificate(inputStream); + } + } + + private static String tlsArtefactPath(String in) { + return in.replace("$(hostname)", hostname()).replace("$(hostname -s)", hostname()); + } + + private static String hostname() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + return Host.hostname(); + } + } + + @FunctionalInterface + interface CallableSupplier { + + T get() throws Exception; + } +} diff --git a/src/test/java/com/rabbitmq/client/test/ssl/UnverifiedConnection.java b/src/test/java/com/rabbitmq/client/test/ssl/UnverifiedConnection.java new file mode 100644 index 0000000000..68cb7b1f39 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ssl/UnverifiedConnection.java @@ -0,0 +1,69 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.ssl; + +import com.rabbitmq.client.GetResponse; +import com.rabbitmq.client.test.BrokerTestCase; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test for bug 19356 - SSL Support in rabbitmq + * + */ +public class UnverifiedConnection extends BrokerTestCase { + + public void openConnection() + throws IOException, TimeoutException { + try { + connectionFactory.useSslProtocol(); + } catch (Exception ex) { + throw new IOException(ex); + } + + int attempt = 0; + while(attempt < 3) { + try { + connection = connectionFactory.newConnection(); + break; + } catch(Exception e) { + LoggerFactory.getLogger(getClass()).warn("Error when opening TLS connection"); + attempt++; + } + } + if(connection == null) { + fail("Couldn't open TLS connection after 3 attempts"); + } + } + + @Test public void sSL() throws IOException + { + channel.queueDeclare("Bug19356Test", false, true, true, null); + channel.basicPublish("", "Bug19356Test", null, "SSL".getBytes()); + + GetResponse chResponse = channel.basicGet("Bug19356Test", false); + assertNotNull(chResponse); + + byte[] body = chResponse.getBody(); + assertEquals("SSL", new String(body)); + } + +} diff --git a/src/test/java/com/rabbitmq/client/test/ssl/VerifiedConnection.java b/src/test/java/com/rabbitmq/client/test/ssl/VerifiedConnection.java new file mode 100644 index 0000000000..39d5094f39 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ssl/VerifiedConnection.java @@ -0,0 +1,110 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + +package com.rabbitmq.client.test.ssl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.impl.nio.NioParams; +import java.io.IOException; +import java.util.Collection; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; + +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.test.TestUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.slf4j.LoggerFactory; + +/** + * Test for bug 19356 - SSL Support in rabbitmq + * + */ +@EnabledForJreRange(min = JRE.JAVA_11) +public class VerifiedConnection extends UnverifiedConnection { + + public void openConnection() + throws IOException, TimeoutException { + try { + SSLContext c = TlsTestUtils.verifiedSslContext(); + connectionFactory = TestUtils.connectionFactory(); + connectionFactory.useSslProtocol(c); + } catch (Exception ex) { + throw new IOException(ex); + } + + int attempt = 0; + while(attempt < 3) { + try { + connection = connectionFactory.newConnection(); + break; + } catch(Exception e) { + LoggerFactory.getLogger(getClass()).warn("Error when opening TLS connection"); + attempt++; + } + } + if(connection == null) { + fail("Couldn't open TLS connection after 3 attempts"); + } + } + + @Test + public void connectionGetConsumeProtocols() throws Exception { + Collection availableProtocols = TlsTestUtils.availableTlsProtocols(); + Collection protocols = Stream.of("TLSv1.2", "TLSv1.3") + .filter(p -> availableProtocols.contains(p)) + .collect(Collectors.toList()); + for (String protocol : protocols) { + SSLContext sslContext = SSLContext.getInstance(protocol); + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.useSslProtocol(TlsTestUtils.verifiedSslContext(() -> sslContext)); + AtomicReference> protocolsSupplier = new AtomicReference<>(); + if (TestUtils.USE_NIO) { + cf.useNio(); + cf.setNioParams(new NioParams() + .setSslEngineConfigurator(sslEngine -> { + protocolsSupplier.set(() -> sslEngine.getEnabledProtocols()); + })); + } else { + cf.setSocketConfigurator(socket -> { + SSLSocket s = (SSLSocket) socket; + protocolsSupplier.set(() -> s.getEnabledProtocols()); + }); + } + try (Connection c = cf.newConnection()) { + CountDownLatch latch = new CountDownLatch(1); + TestUtils.basicGetBasicConsume(c, VerifiedConnection.class.getName(), latch, 100); + boolean messagesReceived = latch.await(5, TimeUnit.SECONDS); + assertTrue(messagesReceived, "Message has not been received"); + assertThat(protocolsSupplier.get()).isNotNull(); + assertThat(protocolsSupplier.get().get()).contains(protocol); + } + } + } + +} diff --git a/src/test/java/com/rabbitmq/tools/Host.java b/src/test/java/com/rabbitmq/tools/Host.java new file mode 100644 index 0000000000..9adf1fc7fc --- /dev/null +++ b/src/test/java/com/rabbitmq/tools/Host.java @@ -0,0 +1,376 @@ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. + + +package com.rabbitmq.tools; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.impl.NetworkConnection; +import com.rabbitmq.client.test.TestUtils; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Host { + + private static final Logger LOGGER = LoggerFactory.getLogger(Host.class); + + private static final String DOCKER_PREFIX = "DOCKER:"; + private static final Pattern CONNECTION_NAME_PATTERN = Pattern.compile("\"connection_name\",\"(?[a-zA-Z0-9\\-]+)?\""); + + public static String hostname() { + try { + return executeCommand("hostname").output(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String capture(InputStream is) + throws IOException + { + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String line; + StringBuilder buff = new StringBuilder(); + while ((line = br.readLine()) != null) { + buff.append(line).append("\n"); + } + return buff.toString(); + } + + public static ProcessState executeCommand(String command) throws IOException + { + Process pr = executeCommandProcess(command); + InputStreamPumpState inputState = new InputStreamPumpState(pr.getInputStream()); + InputStreamPumpState errorState = new InputStreamPumpState(pr.getErrorStream()); + + int ev = waitForExitValue(pr, inputState, errorState); + inputState.pump(); + errorState.pump(); + if (ev != 0) { + throw new IOException("unexpected command exit value: " + ev + + "\ncommand: " + command + "\n" + + "\nstdout:\n" + inputState.buffer.toString() + + "\nstderr:\n" + errorState.buffer.toString() + "\n"); + } + return new ProcessState(pr, inputState, errorState); + } + + public static class ProcessState { + + private final Process process; + private final InputStreamPumpState inputState; + private final InputStreamPumpState errorState; + + ProcessState(Process process, InputStreamPumpState inputState, + InputStreamPumpState errorState) { + this.process = process; + this.inputState = inputState; + this.errorState = errorState; + } + + public String output() { + return inputState.buffer.toString(); + } + + } + + private static class InputStreamPumpState { + + private final BufferedReader reader; + private final StringBuilder buffer; + + private InputStreamPumpState(InputStream in) { + this.reader = new BufferedReader(new InputStreamReader(in)); + this.buffer = new StringBuilder(); + } + + void pump() throws IOException { + String line; + while ((line = reader.readLine()) != null) { + buffer.append(line).append("\n"); + } + } + + } + + private static int waitForExitValue(Process pr, InputStreamPumpState inputState, + InputStreamPumpState errorState) throws IOException { + while(true) { + try { + inputState.pump(); + errorState.pump(); + pr.waitFor(); + break; + } catch (InterruptedException ignored) {} + } + return pr.exitValue(); + } + + public static Process executeCommandIgnoringErrors(String command) throws IOException + { + Process pr = executeCommandProcess(command); + InputStreamPumpState inputState = new InputStreamPumpState(pr.getInputStream()); + InputStreamPumpState errorState = new InputStreamPumpState(pr.getErrorStream()); + inputState.pump(); + errorState.pump(); + boolean exited = false; + try { + exited = pr.waitFor(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + } + if (!exited) { + LOGGER.warn("Command '{}' did not finish in 30 seconds", command); + } + return pr; + } + + private static Process executeCommandProcess(String command) throws IOException + { + String[] finalCommand; + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + finalCommand = new String[4]; + finalCommand[0] = "C:\\winnt\\system32\\cmd.exe"; + finalCommand[1] = "/y"; + finalCommand[2] = "/c"; + finalCommand[3] = command; + } else { + finalCommand = new String[3]; + finalCommand[0] = "/bin/sh"; + finalCommand[1] = "-c"; + finalCommand[2] = command; + } + return Runtime.getRuntime().exec(finalCommand); + } + + public static boolean isRabbitMqCtlCommandAvailable(String command) throws IOException { + Process process = rabbitmqctlIgnoreErrors(command + " --help"); + int exitValue = process.exitValue(); + return exitValue == 0; + } + + public static ProcessState rabbitmqctl(String command) throws IOException { + return executeCommand(rabbitmqctlCommand() + + rabbitmqctlNodenameArgument() + + " " + command); + } + + public static Process rabbitmqctlIgnoreErrors(String command) throws IOException { + return executeCommandIgnoringErrors(rabbitmqctlCommand() + + rabbitmqctlNodenameArgument() + + " " + command); + } + + private static String rabbitmqctlNodenameArgument() { + return isOnDocker() ? "" : " -n \'" + nodenameA() + "\'"; + } + + public static void setResourceAlarm(String source) throws IOException { + rabbitmqctl("eval 'rabbit_alarm:set_alarm({{resource_limit, " + source + ", node()}, []}).'"); + } + + public static void clearResourceAlarm(String source) throws IOException { + rabbitmqctl("eval 'rabbit_alarm:clear_alarm({resource_limit, " + source + ", node()}).'"); + } + + public static void startRabbitOnNode() throws IOException { + rabbitmqctl("start_app"); + tryConnectFor(10_000); + } + + public static void stopRabbitOnNode() throws IOException { + rabbitmqctl("stop_app"); + } + + public static void tryConnectFor(int timeoutInMs) throws IOException { + tryConnectFor(timeoutInMs, 5672); + } + + public static void tryConnectFor(int timeoutInMs, int port) throws IOException { + int waitTime = 100; + int totalWaitTime = 0; + while (totalWaitTime <= timeoutInMs) { + try { + Thread.sleep(waitTime); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + totalWaitTime += waitTime; + ConnectionFactory connectionFactory = TestUtils.connectionFactory(); + connectionFactory.setPort(port); + try (Connection ignored = connectionFactory.newConnection()) { + return; + + } catch (Exception e) { + // retrying + } + } + throw new IOException("Could not connect to broker for " + timeoutInMs + " ms"); + } + + public static String makeCommand() + { + return System.getProperty("make.bin", "make"); + } + + public static String nodenameA() + { + return System.getProperty("test-broker.A.nodename"); + } + + + public static String nodenameB() + { + return System.getProperty("test-broker.B.nodename"); + } + + public static String node_portB() + { + return System.getProperty("test-broker.B.node_port"); + } + + public static String rabbitmqctlCommand() { + String rabbitmqCtl = System.getProperty("rabbitmqctl.bin"); + if (rabbitmqCtl == null) { + throw new IllegalStateException("Please define the rabbitmqctl.bin system property"); + } + if (rabbitmqCtl.startsWith(DOCKER_PREFIX)) { + String containerId = rabbitmqCtl.split(":")[1]; + return "docker exec " + containerId + " rabbitmqctl"; + } else { + return rabbitmqCtl; + } + } + + public static boolean isOnDocker() { + String rabbitmqCtl = System.getProperty("rabbitmqctl.bin"); + if (rabbitmqCtl == null) { + throw new IllegalStateException("Please define the rabbitmqctl.bin system property"); + } + return rabbitmqCtl.startsWith(DOCKER_PREFIX); + } + + public static void closeConnection(String pid) throws IOException { + rabbitmqctl("close_connection '" + pid + "' 'Closed via rabbitmqctl'"); + } + + public static void closeAllConnections() throws IOException { + rabbitmqctl("close_all_connections 'Closed via rabbitmqctl'"); + } + + public static void closeConnection(NetworkConnection c) throws IOException { + Host.ConnectionInfo ci = findConnectionInfoFor(Host.listConnections(), c); + closeConnection(ci.getPid()); + } + + public static class ConnectionInfo { + private final String pid; + private final int peerPort; + private final String clientProperties; + private final String clientProvidedName; + + ConnectionInfo(String pid, int peerPort, String clientProperties, String clientProvidedName) { + this.pid = pid; + this.peerPort = peerPort; + this.clientProperties = clientProperties; + this.clientProvidedName = clientProvidedName; + } + + public String getPid() { + return pid; + } + + public int getPeerPort() { + return peerPort; + } + + public String getClientProperties() { + return clientProperties; + } + + public String getClientProvidedName() { + return clientProvidedName; + } + + boolean hasClientProvidedName() { + return clientProvidedName != null && !clientProvidedName.trim().isEmpty(); + } + + @Override + public String toString() { + return "ConnectionInfo{" + + "pid='" + pid + '\'' + + ", peerPort=" + peerPort + + ", clientProperties='" + clientProperties + '\'' + + ", clientProvidedName='" + clientProvidedName + '\'' + + '}'; + } + } + + public static List listConnections() throws IOException { + String output = rabbitmqctl("list_connections -q pid peer_port client_properties").output(); + // output (header line presence depends on broker version): + // pid peer_port + // 58713 + String[] allLines = output.split("\n"); + List result = new ArrayList(); + for (String line : allLines) { + if (line != null && !line.trim().isEmpty()) { + // line: 58713 + String[] columns = line.split("\t"); + // can be also header line, so ignoring NumberFormatException + try { + int peerPort = Integer.valueOf(columns[1]); + String clientProperties = columns[2]; + String clientProvidedName = extractConnectionName(clientProperties); + result.add(new ConnectionInfo(columns[0], peerPort, clientProperties, clientProvidedName)); + } catch (NumberFormatException e) { + // OK + } + } + } + return result; + } + + private static String extractConnectionName(String clientProperties) { + if (clientProperties.contains("\"connection_name\",")) { + Matcher matcher = CONNECTION_NAME_PATTERN.matcher(clientProperties); + matcher.find(); + return matcher.group("name"); + } else { + return null; + } + } + + private static Host.ConnectionInfo findConnectionInfoFor(List xs, NetworkConnection c) { + Connection conn = (Connection) c; + Predicate predicate = conn.getClientProvidedName() == null ? + ci -> ci.getPeerPort() == c.getLocalPort() : + ci -> ci.hasClientProvidedName() && ci.getClientProvidedName().equals(conn.getClientProvidedName()); + return xs.stream().filter(predicate).findFirst().orElse(null); + } +} diff --git a/test/src/com/rabbitmq/utility/IntAllocatorTests.java b/src/test/java/com/rabbitmq/utility/IntAllocatorTests.java similarity index 58% rename from test/src/com/rabbitmq/utility/IntAllocatorTests.java rename to src/test/java/com/rabbitmq/utility/IntAllocatorTests.java index 5caf10cd29..e75e251d80 100644 --- a/test/src/com/rabbitmq/utility/IntAllocatorTests.java +++ b/src/test/java/com/rabbitmq/utility/IntAllocatorTests.java @@ -1,29 +1,30 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ +// Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. // -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. // +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. package com.rabbitmq.utility; +import org.junit.jupiter.api.Test; + import java.util.HashSet; import java.util.Iterator; import java.util.Random; import java.util.Set; -import junit.framework.TestCase; +import static org.junit.jupiter.api.Assertions.*; -public class IntAllocatorTests extends TestCase { +public class IntAllocatorTests { private static final int TEST_ITERATIONS = 50000; private static final int HI_RANGE = 100000; @@ -32,7 +33,7 @@ public class IntAllocatorTests extends TestCase { private final Random rand = new Random(70608L); - public void testReserveAndFree() throws Exception { + @Test public void reserveAndFree() throws Exception { Set set = new HashSet(); for (int i = 0; i < TEST_ITERATIONS; ++i) { int trial = getTrial(rand); @@ -40,38 +41,38 @@ public void testReserveAndFree() throws Exception { iAll.free(trial); set.remove(trial); } else { - assertTrue("Did not reserve free integer " + trial, iAll.reserve(trial)); + assertTrue(iAll.reserve(trial), "Did not reserve free integer " + trial); set.add(trial); } } for (int trial : set) { - assertFalse("Integer " + trial + " not allocated!", iAll.reserve(trial)); + assertFalse(iAll.reserve(trial), "Integer " + trial + " not allocated!"); } } - public void testAllocateAndFree() throws Exception { + @Test public void allocateAndFree() { Set set = new HashSet(); for (int i=0; i < TEST_ITERATIONS; ++i) { if (getBool(rand)) { int trial = iAll.allocate(); - assertFalse("Already allocated " + trial, set.contains(trial)); + assertFalse(set.contains(trial), "Already allocated " + trial); set.add(trial); } else { if (!set.isEmpty()) { int trial = extractOne(set); - assertFalse("Allocator agreed to reserve " + trial, iAll.reserve(trial)); + assertFalse(iAll.reserve(trial), "Allocator agreed to reserve " + trial); iAll.free(trial); } } } for (int trial : set) { - assertFalse("Integer " + trial + " should be allocated!", iAll.reserve(trial)); + assertFalse(iAll.reserve(trial), "Integer " + trial + " should be allocated!"); } } - public void testToString() throws Exception { + @Test public void testToString() { IntAllocator ibs = new IntAllocator(LO_RANGE, HI_RANGE); assertEquals("IntAllocator{allocated = []}", ibs.toString()); ibs.allocate(); @@ -82,7 +83,7 @@ public void testToString() throws Exception { ibs.reserve(i+2); } assertEquals("IntAllocator{allocated = [100, 200..202, 204..206, 208..210]}" - , ibs.toString()); + , ibs.toString()); } private static int extractOne(Set set) { diff --git a/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 0000000000..5d5a5c135a --- /dev/null +++ b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +com.rabbitmq.client.AmqpClientTestExtension \ No newline at end of file diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 0000000000..b059a65dc4 --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.extensions.autodetection.enabled=true \ No newline at end of file diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..3e3340923e --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/property-file-initialisation/configuration.properties b/src/test/resources/property-file-initialisation/configuration.properties new file mode 100644 index 0000000000..7ec04b03fa --- /dev/null +++ b/src/test/resources/property-file-initialisation/configuration.properties @@ -0,0 +1,25 @@ +rabbitmq.uri=amqp://bar:foo@somewhere:5674/foobar +rabbitmq.username=foo +rabbitmq.password=bar +rabbitmq.virtual.host=dummy +rabbitmq.host=127.0.0.1 +rabbitmq.port=5673 +rabbitmq.connection.channel.max=1 +rabbitmq.connection.frame.max=2 +rabbitmq.connection.heartbeat=10 +rabbitmq.connection.timeout=10000 +rabbitmq.handshake.timeout=5000 +rabbitmq.shutdown.timeout=20000 +rabbitmq.use.default.client.properties=true +rabbitmq.client.properties.foo=bar +rabbitmq.connection.recovery.enabled=false +rabbitmq.topology.recovery.enabled=false +rabbitmq.connection.recovery.interval=10000 +rabbitmq.channel.rpc.timeout=10000 +rabbitmq.channel.should.check.rpc.response.type=true +rabbitmq.use.nio=true +rabbitmq.nio.read.byte.buffer.size=32000 +rabbitmq.nio.write.byte.buffer.size=32000 +rabbitmq.nio.nb.io.threads=2 +rabbitmq.nio.write.enqueuing.timeout.in.ms=5000 +rabbitmq.nio.write.queue.capacity=1000 diff --git a/src/test/resources/property-file-initialisation/tls/keystore.p12 b/src/test/resources/property-file-initialisation/tls/keystore.p12 new file mode 100644 index 0000000000..a5280a6cbf Binary files /dev/null and b/src/test/resources/property-file-initialisation/tls/keystore.p12 differ diff --git a/src/test/resources/property-file-initialisation/tls/truststore.jks b/src/test/resources/property-file-initialisation/tls/truststore.jks new file mode 100644 index 0000000000..4dd357d17a Binary files /dev/null and b/src/test/resources/property-file-initialisation/tls/truststore.jks differ diff --git a/test/src/com/rabbitmq/client/test/AmqpUriTest.java b/test/src/com/rabbitmq/client/test/AmqpUriTest.java deleted file mode 100644 index a477de02c9..0000000000 --- a/test/src/com/rabbitmq/client/test/AmqpUriTest.java +++ /dev/null @@ -1,95 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. -// -// -package com.rabbitmq.client.test; - -import com.rabbitmq.client.ConnectionFactory; - -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.net.URISyntaxException; - -public class AmqpUriTest extends BrokerTestCase -{ - public void testUriParsing() - throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException - { - /* From the spec (subset of the tests) */ - parseSuccess("amqp://user:pass@host:10000/vhost", - "user", "pass", "host", 10000, "vhost"); - parseSuccess("aMQps://user%61:%61pass@host:10000/v%2fhost", - "usera", "apass", "host", 10000, "v/host"); - parseSuccess("amqp://host", "guest", "guest", "host", 5672, "/"); - parseSuccess("amqp:///vhost", - "guest", "guest", "localhost", 5672, "vhost"); - parseSuccess("amqp://host/", "guest", "guest", "host", 5672, ""); - parseSuccess("amqp://host/%2f", "guest", "guest", "host", 5672, "/"); - parseSuccess("amqp://[::1]", "guest", "guest", "[::1]", 5672, "/"); - - /* Various other success cases */ - parseSuccess("amqp://host:100", "guest", "guest", "host", 100, "/"); - parseSuccess("amqp://[::1]:100", "guest", "guest", "[::1]", 100, "/"); - - parseSuccess("amqp://host/blah", - "guest", "guest", "host", 5672, "blah"); - parseSuccess("amqp://host:100/blah", - "guest", "guest", "host", 100, "blah"); - parseSuccess("amqp://[::1]/blah", - "guest", "guest", "[::1]", 5672, "blah"); - parseSuccess("amqp://[::1]:100/blah", - "guest", "guest", "[::1]", 100, "blah"); - - parseSuccess("amqp://user:pass@host", - "user", "pass", "host", 5672, "/"); - parseSuccess("amqp://user:pass@[::1]", - "user", "pass", "[::1]", 5672, "/"); - parseSuccess("amqp://user:pass@[::1]:100", - "user", "pass", "[::1]", 100, "/"); - - /* Various failure cases */ - parseFail("http://www.rabbitmq.com"); - parseFail("amqp://foo[::1]"); - parseFail("amqp://foo:[::1]"); - parseFail("amqp://[::1]foo"); - - parseFail("amqp://foo%1"); - parseFail("amqp://foo%1x"); - parseFail("amqp://foo%xy"); - } - - private void parseSuccess(String uri, String user, String password, - String host, int port, String vhost) - throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException - { - ConnectionFactory cf = new ConnectionFactory(); - cf.setUri(uri); - - assertEquals(user, cf.getUsername()); - assertEquals(password, cf.getPassword()); - assertEquals(host, cf.getHost()); - assertEquals(port, cf.getPort()); - assertEquals(vhost, cf.getVirtualHost()); - } - - private void parseFail(String uri) { - try { - (new ConnectionFactory()).setUri(uri); - fail("URI parse didn't fail: '" + uri + "'"); - } catch (Exception e) { - // whoosh! - } - } -} diff --git a/test/src/com/rabbitmq/client/test/Bug20004Test.java b/test/src/com/rabbitmq/client/test/Bug20004Test.java deleted file mode 100644 index 45947150e6..0000000000 --- a/test/src/com/rabbitmq/client/test/Bug20004Test.java +++ /dev/null @@ -1,77 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test; - -import java.io.IOException; - -/** - * Test for bug 20004 - deadlock through internal synchronization on - * the channel object. This is more properly a unit test, but since it - * requires a connection to a broker, it's grouped with the functional - * tests. - *

- * Test calls channel.queueDeclare, while synchronising on channel, from - * an independent thread. - */ -public class Bug20004Test extends BrokerTestCase { - private volatile Exception caughtException = null; - private volatile boolean completed = false; - private volatile boolean created = false; - - protected void releaseResources() - throws IOException - { - if (created) { - channel.queueDelete("Bug20004Test"); - } - } - - @SuppressWarnings("deprecation") - public void testBug20004() throws IOException - { - final Bug20004Test testInstance = this; - - Thread declaringThread = new Thread(new Runnable() { - public void run() { - try { - synchronized (channel) { - channel.queueDeclare("Bug20004Test", false, false, false, null); - testInstance.created = true; - } - } catch (Exception e) { - testInstance.caughtException = e; - } - testInstance.completed = true; - } - }); - declaringThread.start(); - - // poll (100ms) for `completed`, up to 5s - long endTime = System.currentTimeMillis() + 5000; - while (!completed && (System.currentTimeMillis() < endTime)) { - try { - Thread.sleep(100); - } catch (InterruptedException ie) {} - } - - declaringThread.stop(); // see bug 20012. - - assertTrue("Deadlock detected?", completed); - assertNull("queueDeclare threw an exception", caughtException); - assertTrue("unknown sequence of events", created); - } -} diff --git a/test/src/com/rabbitmq/client/test/ChannelNumberAllocationTests.java b/test/src/com/rabbitmq/client/test/ChannelNumberAllocationTests.java deleted file mode 100644 index 13d00eb361..0000000000 --- a/test/src/com/rabbitmq/client/test/ChannelNumberAllocationTests.java +++ /dev/null @@ -1,120 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import junit.framework.TestCase; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; - -public class ChannelNumberAllocationTests extends TestCase{ - static final int CHANNEL_COUNT = 100; - static final Comparator COMPARATOR = new Comparator(){ - public int compare(Channel x, Channel y){ - if(x.getChannelNumber() < y.getChannelNumber()) return -1; - if(x.getChannelNumber() == y.getChannelNumber()) return 0; - return 1; - } - }; - - Connection connection; - - public void setUp() throws Exception{ - connection = new ConnectionFactory().newConnection(); - } - - public void tearDown() throws Exception{ - connection.close(); - connection = null; - } - - public void testAllocateInOrder() throws Exception{ - for(int i = 1; i <= CHANNEL_COUNT; i++) - assertEquals(i, connection.createChannel().getChannelNumber()); - } - - public void testAllocateAfterFreeingLast() throws Exception{ - Channel ch = connection.createChannel(); - assertEquals(1, ch.getChannelNumber()); - ch.close(); - ch = connection.createChannel(); - assertEquals(1, ch.getChannelNumber()); - } - - public void testAllocateAfterFreeingMany() throws Exception{ - List channels = new ArrayList(); - - for(int i = 1; i <= CHANNEL_COUNT; i++) - channels.add(connection.createChannel()); - - for(Channel channel : channels){ - channel.close(); - } - - channels = new ArrayList(); - - for(int i = 1; i <= CHANNEL_COUNT; i++) - channels.add(connection.createChannel()); - - // In the current implementation the allocated numbers need not be increasing - Collections.sort(channels, COMPARATOR); - - assertEquals("Didn't create the right number of channels!", CHANNEL_COUNT, channels.size()); - for(int i = 1; i < CHANNEL_COUNT; ++i) { - assertTrue("Channel numbers should be distinct." - , channels.get(i-1).getChannelNumber() < channels.get(i).getChannelNumber() - ); - } - } - - public void testAllocateAfterManualAssign() throws Exception{ - connection.createChannel(10); - - for(int i = 0; i < 20; i++) - assertTrue(10 != connection.createChannel().getChannelNumber()); - } - - public void testManualAllocationDoesntBreakThings() throws Exception{ - connection.createChannel((1 << 16) - 1); - Channel ch = connection.createChannel(); - assertNotNull(ch); - } - - public void testReuseManuallyAllocatedChannelNumber1() throws Exception{ - connection.createChannel(1).close(); - assertNotNull(connection.createChannel(1)); - } - - public void testReuseManuallyAllocatedChannelNumber2() throws Exception{ - connection.createChannel(2).close(); - assertNotNull(connection.createChannel(3)); - } - - public void testReserveOnBoundaries() throws Exception{ - assertNotNull(connection.createChannel(3)); - assertNotNull(connection.createChannel(4)); - assertNotNull(connection.createChannel(2)); - assertNotNull(connection.createChannel(5)); - assertNotNull(connection.createChannel(1)); - } -} diff --git a/test/src/com/rabbitmq/client/test/ClientTests.java b/test/src/com/rabbitmq/client/test/ClientTests.java deleted file mode 100644 index d618da89f1..0000000000 --- a/test/src/com/rabbitmq/client/test/ClientTests.java +++ /dev/null @@ -1,45 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test; - -import junit.framework.TestCase; -import junit.framework.TestSuite; - -public class ClientTests extends TestCase { - public static TestSuite suite() { - TestSuite suite = new TestSuite("client"); - suite.addTest(TableTest.suite()); - suite.addTest(BlockingCellTest.suite()); - suite.addTest(TruncatedInputStreamTest.suite()); - suite.addTest(AMQConnectionTest.suite()); - suite.addTest(ValueOrExceptionTest.suite()); - suite.addTest(BrokenFramesTest.suite()); - suite.addTest(ClonePropertiesTest.suite()); - suite.addTestSuite(Bug20004Test.class); - suite.addTestSuite(CloseInMainLoop.class); - suite.addTestSuite(ChannelNumberAllocationTests.class); - suite.addTestSuite(QueueingConsumerShutdownTests.class); - suite.addTestSuite(MultiThreadedChannel.class); - suite.addTestSuite(com.rabbitmq.utility.IntAllocatorTests.class); - suite.addTestSuite(AMQBuilderApiTest.class); - suite.addTestSuite(AmqpUriTest.class); - suite.addTestSuite(JSONReadWriteTest.class); - suite.addTestSuite(SharedThreadPoolTest.class); - return suite; - } -} diff --git a/test/src/com/rabbitmq/client/test/ClonePropertiesTest.java b/test/src/com/rabbitmq/client/test/ClonePropertiesTest.java deleted file mode 100644 index 186ad84ea1..0000000000 --- a/test/src/com/rabbitmq/client/test/ClonePropertiesTest.java +++ /dev/null @@ -1,51 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test; - -import junit.framework.TestCase; -import junit.framework.TestSuite; - -import com.rabbitmq.client.AMQP.BasicProperties; -import com.rabbitmq.client.MessageProperties; - -public class ClonePropertiesTest extends TestCase { - - public static TestSuite suite() - { - TestSuite suite = new TestSuite("cloneProperties"); - suite.addTestSuite(ClonePropertiesTest.class); - return suite; - } - - public void testPropertyCloneIsDistinct() - throws CloneNotSupportedException - { - assertTrue(MessageProperties.MINIMAL_PERSISTENT_BASIC != - MessageProperties.MINIMAL_PERSISTENT_BASIC.clone()); - } - - public void testPropertyClonePreservesValues() - throws CloneNotSupportedException - { - assertEquals(MessageProperties.MINIMAL_PERSISTENT_BASIC.getDeliveryMode(), - ((BasicProperties) MessageProperties.MINIMAL_PERSISTENT_BASIC.clone()) - .getDeliveryMode()); - assertEquals(new Integer(2), - ((BasicProperties) MessageProperties.MINIMAL_PERSISTENT_BASIC.clone()) - .getDeliveryMode()); - } -} diff --git a/test/src/com/rabbitmq/client/test/JSONReadWriteTest.java b/test/src/com/rabbitmq/client/test/JSONReadWriteTest.java deleted file mode 100644 index 4b1a9d6aa5..0000000000 --- a/test/src/com/rabbitmq/client/test/JSONReadWriteTest.java +++ /dev/null @@ -1,115 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - -import com.rabbitmq.tools.json.JSONWriter; -import com.rabbitmq.tools.json.JSONReader; - -import junit.framework.TestCase; - -public class JSONReadWriteTest extends TestCase { - - public void testReadWriteSimple() throws Exception { - - Object myRet; - String myJson; - - // simple string - myRet = new JSONReader().read(myJson = new JSONWriter().write("blah")); - assertEquals("blah", myRet); - - // simple int - myRet = new JSONReader().read(myJson = new JSONWriter().write(1)); - assertEquals(1, myRet); - - // string with double quotes - myRet = new JSONReader().read(myJson = new JSONWriter().write("t1-blah\"blah")); - assertEquals("t1-blah\"blah", myRet); - // string with single quotes - myRet = new JSONReader().read(myJson = new JSONWriter().write("t2-blah'blah")); - assertEquals("t2-blah'blah", myRet); - // string with two double quotes - myRet = new JSONReader().read(myJson = new JSONWriter().write("t3-blah\"n\"blah")); - assertEquals("t3-blah\"n\"blah", myRet); - // string with two single quotes - myRet = new JSONReader().read(myJson = new JSONWriter().write("t4-blah'n'blah")); - assertEquals("t4-blah'n'blah", myRet); - // string with a single and a double quote - myRet = new JSONReader().read(myJson = new JSONWriter().write("t4-blah'n\"blah")); - assertEquals("t4-blah'n\"blah", myRet); - - // UTF-8 character - myRet = new JSONReader().read(myJson = new JSONWriter().write("smile \u9786")); - assertEquals("smile \u9786", myRet); - - // null byte - myRet = new JSONReader().read(myJson = new JSONWriter().write("smile \u0000")); - assertEquals("smile \u0000", myRet); - - } - - public void testMoreComplicated() throws Exception { - - String v, s; - Object t; - - s = "[\"foo\",{\"bar\":[\"baz\",null,1.0,2]}]"; - v = new JSONWriter().write(new JSONReader().read(s)); - assertEquals(s, v); - - s = "[\"foo\",{\"bar\":[\"b\\\"az\",null,1.0,2]}]"; - t = new JSONReader().read(s); - v = new JSONWriter().write(t); - assertEquals(s, v); - - s = "[\"foo\",{\"bar\":[\"b'az\",null,1.0,2]}]"; - v = new JSONWriter().write(new JSONReader().read(s)); - assertEquals(s, v); - - s = "[\"foo\",{\"bar\":[\"b'a'z\",null,1.0,2]}]"; - v = new JSONWriter().write(new JSONReader().read(s)); - assertEquals(s, v); - - s = "[\"foo\",{\"bar\":[\"b\\\"a\\\"z\",null,1.0,2]}]"; - v = new JSONWriter().write(new JSONReader().read(s)); - assertEquals(s, v); - - } - - public void testBadJSON() throws Exception { - - try { - new JSONReader().read("[\"foo\",{\"bar\":[\"b\"az\",null,1.0,2]}]"); - fail("Should not have parsed"); - } - catch (IllegalStateException e) {} - - try { - new JSONReader().read("[\"foo\",{\"bar\":[\"b\"a\"z\",null,1.0,2]}]"); - fail("Should not have parsed"); - } - catch (IllegalStateException e) {} - - } - -} diff --git a/test/src/com/rabbitmq/client/test/QueueingConsumerShutdownTests.java b/test/src/com/rabbitmq/client/test/QueueingConsumerShutdownTests.java deleted file mode 100644 index abd4be91ff..0000000000 --- a/test/src/com/rabbitmq/client/test/QueueingConsumerShutdownTests.java +++ /dev/null @@ -1,64 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.QueueingConsumer; -import com.rabbitmq.client.ShutdownSignalException; - -public class QueueingConsumerShutdownTests extends BrokerTestCase{ - static final String QUEUE = "some-queue"; - static final int THREADS = 5; - - public void testNThreadShutdown() throws Exception{ - Channel channel = connection.createChannel(); - final QueueingConsumer c = new QueueingConsumer(channel); - channel.queueDeclare(QUEUE, false, true, true, null); - channel.basicConsume(QUEUE, c); - final AtomicInteger count = new AtomicInteger(THREADS); - final CountDownLatch latch = new CountDownLatch(THREADS); - - for(int i = 0; i < THREADS; i++){ - new Thread(){ - @Override public void run(){ - try { - while(true){ - c.nextDelivery(); - } - } catch (ShutdownSignalException sig) { - count.decrementAndGet(); - } catch (Exception e) { - throw new RuntimeException(e); - } finally { - latch.countDown(); - } - } - }.start(); - } - - connection.close(); - - // Far longer than this could reasonably take - assertTrue(latch.await(5, TimeUnit.SECONDS)); - assertEquals(0, count.get()); - } - -} diff --git a/test/src/com/rabbitmq/client/test/SharedThreadPoolTest.java b/test/src/com/rabbitmq/client/test/SharedThreadPoolTest.java deleted file mode 100644 index ee828e1d42..0000000000 --- a/test/src/com/rabbitmq/client/test/SharedThreadPoolTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.rabbitmq.client.test; - -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.impl.AMQConnection; - -import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class SharedThreadPoolTest extends BrokerTestCase { - public void testWillShutDownExecutor() throws IOException { - ConnectionFactory cf = new ConnectionFactory(); - ExecutorService executor = Executors.newFixedThreadPool(8); - cf.setSharedExecutor(executor); - - AMQConnection conn1 = (AMQConnection)cf.newConnection(); - assertFalse(conn1.willShutDownConsumerExecutor()); - - AMQConnection conn2 = (AMQConnection)cf.newConnection(Executors.newSingleThreadExecutor()); - assertFalse(conn2.willShutDownConsumerExecutor()); - - AMQConnection conn3 = (AMQConnection)cf.newConnection((ExecutorService)null); - assertTrue(conn3.willShutDownConsumerExecutor()); - - cf.setSharedExecutor(null); - - AMQConnection conn4 = (AMQConnection)cf.newConnection(); - assertTrue(conn4.willShutDownConsumerExecutor()); - } -} diff --git a/test/src/com/rabbitmq/client/test/TableTest.java b/test/src/com/rabbitmq/client/test/TableTest.java deleted file mode 100644 index 9a40188295..0000000000 --- a/test/src/com/rabbitmq/client/test/TableTest.java +++ /dev/null @@ -1,96 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.math.BigDecimal; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import junit.framework.TestCase; -import junit.framework.TestSuite; - -import com.rabbitmq.client.impl.Frame; -import com.rabbitmq.client.impl.LongStringHelper; -import com.rabbitmq.client.impl.MethodArgumentReader; -import com.rabbitmq.client.impl.MethodArgumentWriter; -import com.rabbitmq.client.impl.ValueReader; -import com.rabbitmq.client.impl.ValueWriter; - -public class TableTest - extends TestCase -{ - public static TestSuite suite() - { - TestSuite suite = new TestSuite("tables"); - suite.addTestSuite(TableTest.class); - return suite; - } - - public byte [] marshal(Map table) - throws IOException - { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - MethodArgumentWriter writer = new MethodArgumentWriter(new ValueWriter(new DataOutputStream(buffer))); - writer.writeTable(table); - writer.flush(); - - assertEquals(Frame.tableSize(table) + 4, buffer.size()); - return buffer.toByteArray(); - } - - public Map unmarshal(byte [] bytes) - throws IOException - { - MethodArgumentReader reader = - new MethodArgumentReader - (new ValueReader - (new DataInputStream - (new ByteArrayInputStream(bytes)))); - - return reader.readTable(); - } - - public Date secondDate() - { - return new Date((System.currentTimeMillis()/1000)*1000); - } - - public void testLoop() - throws IOException - { - Map table = new HashMap(); - table.put("a", 1); - assertEquals(table, unmarshal(marshal(table))); - - table.put("b", secondDate()); - assertEquals(table, unmarshal(marshal(table))); - - table.put("c", new BigDecimal("1.1")); - assertEquals(table, unmarshal(marshal(table))); - - table.put("d", LongStringHelper.asLongString("d")); - assertEquals(table, unmarshal(marshal(table))); - - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/AbstractRejectTest.java b/test/src/com/rabbitmq/client/test/functional/AbstractRejectTest.java deleted file mode 100644 index f6c88eb10f..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/AbstractRejectTest.java +++ /dev/null @@ -1,74 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.QueueingConsumer; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; -import java.util.Arrays; - -abstract class AbstractRejectTest extends BrokerTestCase { - - protected Channel secondaryChannel; - - @Override - protected void setUp() - throws IOException - { - super.setUp(); - secondaryChannel = connection.createChannel(); - - } - - @Override - protected void tearDown() - throws IOException - { - if (secondaryChannel != null) { - secondaryChannel.abort(); - secondaryChannel = null; - } - super.tearDown(); - } - - protected long checkDelivery(QueueingConsumer.Delivery d, - byte[] msg, boolean redelivered) - { - assertNotNull(d); - return checkDelivery(d.getEnvelope(), d.getBody(), msg, redelivered); - } - - protected long checkDelivery(GetResponse r, byte[] msg, boolean redelivered) - { - assertNotNull(r); - return checkDelivery(r.getEnvelope(), r.getBody(), msg, redelivered); - } - - protected long checkDelivery(Envelope e, byte[] m, - byte[] msg, boolean redelivered) - { - assertNotNull(e); - assertTrue(Arrays.equals(m, msg)); - assertEquals(e.isRedeliver(), redelivered); - return e.getDeliveryTag(); - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/BasicGet.java b/test/src/com/rabbitmq/client/test/functional/BasicGet.java deleted file mode 100644 index 9c7b5194e4..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/BasicGet.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.AlreadyClosedException; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; - -public class BasicGet extends BrokerTestCase { - public void testBasicGetWithEnqueuedMessages() throws IOException, InterruptedException { - assertTrue(channel.isOpen()); - String q = channel.queueDeclare().getQueue(); - - basicPublishPersistent("msg".getBytes("UTF-8"), q); - Thread.sleep(250); - - assertNotNull(channel.basicGet(q, true)); - channel.queuePurge(q); - assertNull(channel.basicGet(q, true)); - channel.queueDelete(q); - } - - public void testBasicGetWithEmptyQueue() throws IOException, InterruptedException { - assertTrue(channel.isOpen()); - String q = channel.queueDeclare().getQueue(); - - assertNull(channel.basicGet(q, true)); - channel.queueDelete(q); - } - - public void testBasicGetWithClosedChannel() throws IOException, InterruptedException { - assertTrue(channel.isOpen()); - String q = channel.queueDeclare().getQueue(); - - channel.close(); - assertFalse(channel.isOpen()); - try { - channel.basicGet(q, true); - fail("expected basic.get on a closed channel to fail"); - } catch (AlreadyClosedException e) { - // passed - } finally { - Channel tch = connection.createChannel(); - tch.queueDelete(q); - tch.close(); - } - - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/CcRoutes.java b/test/src/com/rabbitmq/client/test/functional/CcRoutes.java deleted file mode 100644 index 6be0bf2ed2..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/CcRoutes.java +++ /dev/null @@ -1,148 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.AMQP.BasicProperties; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -public class CcRoutes extends BrokerTestCase { - - static private final String[] queues = new String[]{"queue1", "queue2", "queue3"}; - protected final String exDirect = "direct_cc_exchange"; - protected final String exTopic = "topic_cc_exchange"; - protected BasicProperties.Builder propsBuilder; - protected Map headers; - protected List ccList; - protected List bccList; - - @Override protected void setUp() throws IOException { - super.setUp(); - propsBuilder = new BasicProperties.Builder(); - headers = new HashMap(); - ccList = new ArrayList(); - bccList = new ArrayList(); - } - - @Override protected void createResources() throws IOException { - super.createResources(); - for (String q : queues) { - channel.queueDeclare(q, false, true, true, null); - } - channel.exchangeDeclare(exDirect, "direct", false, true, null); - channel.exchangeDeclare(exTopic, "topic", false, true, null); - } - - public void testCcList() throws IOException { - ccList.add("queue2"); - ccList.add("queue3"); - headerPublish("", "queue1", ccList, null); - expect(new String []{"queue1", "queue2", "queue3"}, true); - } - - public void testCcIgnoreEmptyAndInvalidRoutes() throws IOException { - bccList.add("frob"); - headerPublish("", "queue1", ccList, bccList); - expect(new String []{"queue1"}, true); - } - - public void testBcc() throws IOException { - bccList.add("queue2"); - headerPublish("", "queue1", null, bccList); - expect(new String []{"queue1", "queue2"}, false); - } - - public void testNoDuplicates() throws IOException { - ccList.add("queue1"); - ccList.add("queue1"); - bccList.add("queue1"); - headerPublish("", "queue1", ccList, bccList); - expect(new String[] {"queue1"}, true); - } - - public void testDirectExchangeWithoutBindings() throws IOException { - ccList.add("queue1"); - headerPublish(exDirect, "queue2", ccList, null); - expect(new String[] {}, true); - } - - public void testTopicExchange() throws IOException { - ccList.add("routing_key"); - channel.queueBind("queue2", exTopic, "routing_key"); - headerPublish(exTopic, "", ccList, null); - expect(new String[] {"queue2"}, true); - } - - public void testBoundExchanges() throws IOException { - ccList.add("routing_key1"); - bccList.add("routing_key2"); - channel.exchangeBind(exTopic, exDirect, "routing_key1"); - channel.queueBind("queue2", exTopic, "routing_key2"); - headerPublish(exDirect, "", ccList, bccList); - expect(new String[] {"queue2"}, true); - } - - public void testNonArray() throws IOException { - headers.put("CC", 0); - propsBuilder.headers(headers); - channel.basicPublish("", "queue1", propsBuilder.build(), new byte[0]); - try { - expect(new String[] {}, false); - fail(); - } catch (IOException e) { - checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); - } - } - - private void headerPublish(String ex, String to, List cc, List bcc) throws IOException { - if (cc != null) { - headers.put("CC", ccList); - } - if (bcc != null) { - headers.put("BCC", bccList); - } - propsBuilder.headers(headers); - channel.basicPublish(ex, to, propsBuilder.build(), new byte[0]); - } - - private void expect(String[] expectedQueues, boolean usedCc) throws IOException { - GetResponse getResponse; - List expectedList = Arrays.asList(expectedQueues); - for (String q : queues) { - getResponse = basicGet(q); - if (expectedList.contains(q)) { - assertNotNull(getResponse); - assertEquals(0, getResponse.getMessageCount()); - Map headers = getResponse.getProps().getHeaders(); - if (headers != null){ - assertEquals(usedCc, headers.containsKey("CC")); - assertFalse(headers.containsKey("BCC")); - } - } else { - assertNull(getResponse); - } - } - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/ConnectionRecovery.java b/test/src/com/rabbitmq/client/test/functional/ConnectionRecovery.java deleted file mode 100644 index 5adc88c503..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/ConnectionRecovery.java +++ /dev/null @@ -1,741 +0,0 @@ -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.*; -import com.rabbitmq.client.impl.recovery.AutorecoveringChannel; -import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; -import com.rabbitmq.client.Recoverable; -import com.rabbitmq.client.RecoveryListener; -import com.rabbitmq.client.impl.recovery.ConsumerRecoveryListener; -import com.rabbitmq.client.impl.recovery.QueueRecoveryListener; -import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.tools.Host; - -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -public class ConnectionRecovery extends BrokerTestCase { - public static final long RECOVERY_INTERVAL = 2000; - - public void testConnectionRecovery() throws IOException, InterruptedException { - assertTrue(connection.isOpen()); - closeAndWaitForRecovery(); - assertTrue(connection.isOpen()); - } - - public void testConnectionRecoveryWithServerRestart() throws IOException, InterruptedException { - assertTrue(connection.isOpen()); - restartPrimaryAndWaitForRecovery(); - assertTrue(connection.isOpen()); - } - - public void testConnectionRecoveryWithMultipleAddresses() throws IOException, InterruptedException { - final Address[] addresses = {new Address("127.0.0.1"), new Address("127.0.0.1", 5672)}; - AutorecoveringConnection c = newRecoveringConnection(addresses); - try { - assertTrue(c.isOpen()); - closeAndWaitForRecovery(c); - assertTrue(c.isOpen()); - } finally { - c.abort(); - } - - } - - public void testConnectionRecoveryWithDisabledTopologyRecovery() throws IOException, InterruptedException { - AutorecoveringConnection c = newRecoveringConnection(true); - Channel ch = c.createChannel(); - String q = "java-client.test.recovery.q2"; - ch.queueDeclare(q, false, true, false, null); - ch.queueDeclarePassive(q); - assertTrue(c.isOpen()); - try { - CountDownLatch shutdownLatch = prepareForShutdown(c); - CountDownLatch recoveryLatch = prepareForRecovery(c); - Host.closeConnection(c); - wait(shutdownLatch); - wait(recoveryLatch); - assertTrue(c.isOpen()); - ch.queueDeclarePassive(q); - fail("expected passive declaration to throw"); - } catch (java.io.IOException e) { - // expected - } finally { - c.abort(); - } - } - - public void testShutdownHooksRecoveryOnConnection() throws IOException, InterruptedException { - final CountDownLatch latch = new CountDownLatch(2); - connection.addShutdownListener(new ShutdownListener() { - public void shutdownCompleted(ShutdownSignalException cause) { - latch.countDown(); - } - }); - assertTrue(connection.isOpen()); - closeAndWaitForRecovery(); - assertTrue(connection.isOpen()); - connection.close(); - wait(latch); - } - - public void testShutdownHooksRecoveryOnChannel() throws IOException, InterruptedException { - final CountDownLatch latch = new CountDownLatch(3); - channel.addShutdownListener(new ShutdownListener() { - public void shutdownCompleted(ShutdownSignalException cause) { - latch.countDown(); - } - }); - assertTrue(connection.isOpen()); - closeAndWaitForRecovery(); - assertTrue(connection.isOpen()); - closeAndWaitForRecovery(); - assertTrue(connection.isOpen()); - connection.close(); - wait(latch); - } - - public void testBlockedListenerRecovery() throws IOException, InterruptedException { - final CountDownLatch latch = new CountDownLatch(2); - connection.addBlockedListener(new BlockedListener() { - public void handleBlocked(String reason) throws IOException { - latch.countDown(); - } - - public void handleUnblocked() throws IOException { - latch.countDown(); - } - }); - closeAndWaitForRecovery(); - block(); - channel.basicPublish("", "", null, "".getBytes()); - unblock(); - wait(latch); - } - - public void testChannelRecovery() throws IOException, InterruptedException { - Channel ch1 = connection.createChannel(); - Channel ch2 = connection.createChannel(); - - assertTrue(ch1.isOpen()); - assertTrue(ch2.isOpen()); - closeAndWaitForRecovery(); - expectChannelRecovery(ch1); - expectChannelRecovery(ch2); - } - - public void testReturnListenerRecovery() throws IOException, InterruptedException { - final CountDownLatch latch = new CountDownLatch(1); - channel.addReturnListener(new ReturnListener() { - public void handleReturn(int replyCode, String replyText, String exchange, - String routingKey, AMQP.BasicProperties properties, - byte[] body) throws IOException { - latch.countDown(); - } - }); - closeAndWaitForRecovery(); - expectChannelRecovery(channel); - channel.basicPublish("", "unknown", true, false, null, "mandatory1".getBytes()); - wait(latch); - } - - public void testConfirmListenerRecovery() throws IOException, InterruptedException, TimeoutException { - final CountDownLatch latch = new CountDownLatch(1); - channel.addConfirmListener(new ConfirmListener() { - public void handleAck(long deliveryTag, boolean multiple) throws IOException { - latch.countDown(); - } - - public void handleNack(long deliveryTag, boolean multiple) throws IOException { - latch.countDown(); - } - }); - String q = channel.queueDeclare(UUID.randomUUID().toString(), false, false, false, null).getQueue(); - closeAndWaitForRecovery(); - expectChannelRecovery(channel); - channel.confirmSelect(); - basicPublishVolatile(q); - waitForConfirms(channel); - wait(latch); - } - - public void testExchangeRecovery() throws IOException, InterruptedException, TimeoutException { - Channel ch = connection.createChannel(); - String x = "java-client.test.recovery.x1"; - declareExchange(ch, x); - closeAndWaitForRecovery(); - expectChannelRecovery(ch); - expectExchangeRecovery(ch, x); - ch.exchangeDelete(x); - } - - public void testExchangeRecoveryWithNoWait() throws IOException, InterruptedException, TimeoutException { - Channel ch = connection.createChannel(); - String x = "java-client.test.recovery.x1-nowait"; - declareExchangeNoWait(ch, x); - closeAndWaitForRecovery(); - expectChannelRecovery(ch); - expectExchangeRecovery(ch, x); - ch.exchangeDelete(x); - } - - public void testClientNamedQueueRecovery() throws IOException, InterruptedException, TimeoutException { - testClientNamedQueueRecoveryWith("java-client.test.recovery.q1", false); - } - - public void testClientNamedQueueRecoveryWithNoWait() throws IOException, InterruptedException, TimeoutException { - testClientNamedQueueRecoveryWith("java-client.test.recovery.q1-nowait", true); - } - - protected void testClientNamedQueueRecoveryWith(String q, boolean noWait) throws IOException, InterruptedException, TimeoutException { - Channel ch = connection.createChannel(); - if(noWait) { - declareClientNamedQueueNoWait(ch, q); - } else { - declareClientNamedQueue(ch, q); - } - closeAndWaitForRecovery(); - expectChannelRecovery(ch); - expectQueueRecovery(ch, q); - ch.queueDelete(q); - } - - public void testClientNamedQueueBindingRecovery() throws IOException, InterruptedException, TimeoutException { - String q = "java-client.test.recovery.q2"; - String x = "tmp-fanout"; - Channel ch = connection.createChannel(); - ch.queueDelete(q); - ch.exchangeDelete(x); - ch.exchangeDeclare(x, "fanout"); - declareClientNamedAutoDeleteQueue(ch, q); - ch.queueBind(q, x, ""); - closeAndWaitForRecovery(); - expectChannelRecovery(ch); - expectAutoDeleteQueueAndBindingRecovery(ch, x, q); - ch.queueDelete(q); - ch.exchangeDelete(x); - } - - // bug 26552 - public void testClientNamedTransientAutoDeleteQueueAndBindingRecovery() throws IOException, InterruptedException, TimeoutException { - String q = UUID.randomUUID().toString(); - String x = "tmp-fanout"; - Channel ch = connection.createChannel(); - ch.queueDelete(q); - ch.exchangeDelete(x); - ch.exchangeDeclare(x, "fanout"); - ch.queueDeclare(q, false, false, true, null); - ch.queueBind(q, x, ""); - restartPrimaryAndWaitForRecovery(); - expectChannelRecovery(ch); - ch.confirmSelect(); - ch.queuePurge(q); - ch.exchangeDeclare(x, "fanout"); - ch.basicPublish(x, "", null, "msg".getBytes()); - waitForConfirms(ch); - AMQP.Queue.DeclareOk ok = ch.queueDeclare(q, false, false, true, null); - assertEquals(1, ok.getMessageCount()); - ch.queueDelete(q); - ch.exchangeDelete(x); - } - - // bug 26552 - public void testServerNamedTransientAutoDeleteQueueAndBindingRecovery() throws IOException, InterruptedException, TimeoutException { - String x = "tmp-fanout"; - Channel ch = connection.createChannel(); - ch.exchangeDelete(x); - ch.exchangeDeclare(x, "fanout"); - String q = ch.queueDeclare("", false, false, true, null).getQueue(); - final AtomicReference nameBefore = new AtomicReference(q); - final AtomicReference nameAfter = new AtomicReference(); - final CountDownLatch listenerLatch = new CountDownLatch(1); - ((AutorecoveringConnection)connection).addQueueRecoveryListener(new QueueRecoveryListener() { - @Override - public void queueRecovered(String oldName, String newName) { - nameBefore.set(oldName); - nameAfter.set(newName); - listenerLatch.countDown(); - } - }); - ch.queueBind(nameBefore.get(), x, ""); - restartPrimaryAndWaitForRecovery(); - expectChannelRecovery(ch); - ch.confirmSelect(); - ch.exchangeDeclare(x, "fanout"); - ch.basicPublish(x, "", null, "msg".getBytes()); - waitForConfirms(ch); - AMQP.Queue.DeclareOk ok = ch.queueDeclarePassive(nameAfter.get()); - assertEquals(1, ok.getMessageCount()); - ch.queueDelete(nameAfter.get()); - ch.exchangeDelete(x); - } - - public void testDeclarationOfManyAutoDeleteQueuesWithTransientConsumer() throws IOException { - Channel ch = connection.createChannel(); - assertRecordedQueues(connection, 0); - for(int i = 0; i < 5000; i++) { - String q = UUID.randomUUID().toString(); - ch.queueDeclare(q, false, false, true, null); - QueueingConsumer dummy = new QueueingConsumer(ch); - String tag = ch.basicConsume(q, true, dummy); - ch.basicCancel(tag); - } - assertRecordedQueues(connection, 0); - ch.close(); - } - - public void testDeclarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreUnbound() throws IOException { - Channel ch = connection.createChannel(); - assertRecordedExchanges(connection, 0); - for(int i = 0; i < 5000; i++) { - String x = UUID.randomUUID().toString(); - ch.exchangeDeclare(x, "fanout", false, true, null); - String q = ch.queueDeclare().getQueue(); - final String rk = "doesn't matter"; - ch.queueBind(q, x, rk); - ch.queueUnbind(q, x, rk); - ch.queueDelete(q); - } - assertRecordedExchanges(connection, 0); - ch.close(); - } - - public void testDeclarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreDeleted() throws IOException { - Channel ch = connection.createChannel(); - assertRecordedExchanges(connection, 0); - for(int i = 0; i < 5000; i++) { - String x = UUID.randomUUID().toString(); - ch.exchangeDeclare(x, "fanout", false, true, null); - String q = ch.queueDeclare().getQueue(); - ch.queueBind(q, x, "doesn't matter"); - ch.queueDelete(q); - } - assertRecordedExchanges(connection, 0); - ch.close(); - } - - public void testDeclarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreUnbound() throws IOException { - Channel ch = connection.createChannel(); - assertRecordedExchanges(connection, 0); - for(int i = 0; i < 5000; i++) { - String src = "src-" + UUID.randomUUID().toString(); - String dest = "dest-" + UUID.randomUUID().toString(); - ch.exchangeDeclare(src, "fanout", false, true, null); - ch.exchangeDeclare(dest, "fanout", false, true, null); - final String rk = "doesn't matter"; - ch.exchangeBind(dest, src, rk); - ch.exchangeUnbind(dest, src, rk); - ch.exchangeDelete(dest); - } - assertRecordedExchanges(connection, 0); - ch.close(); - } - - public void testDeclarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreDeleted() throws IOException { - Channel ch = connection.createChannel(); - assertRecordedExchanges(connection, 0); - for(int i = 0; i < 5000; i++) { - String src = "src-" + UUID.randomUUID().toString(); - String dest = "dest-" + UUID.randomUUID().toString(); - ch.exchangeDeclare(src, "fanout", false, true, null); - ch.exchangeDeclare(dest, "fanout", false, true, null); - ch.exchangeBind(dest, src, "doesn't matter"); - ch.exchangeDelete(dest); - } - assertRecordedExchanges(connection, 0); - ch.close(); - } - - public void testServerNamedQueueRecovery() throws IOException, InterruptedException { - String q = channel.queueDeclare("", false, false, false, null).getQueue(); - String x = "amq.fanout"; - channel.queueBind(q, x, ""); - - final AtomicReference nameBefore = new AtomicReference(); - final AtomicReference nameAfter = new AtomicReference(); - final CountDownLatch listenerLatch = new CountDownLatch(1); - ((AutorecoveringConnection)connection).addQueueRecoveryListener(new QueueRecoveryListener() { - @Override - public void queueRecovered(String oldName, String newName) { - nameBefore.set(oldName); - nameAfter.set(newName); - listenerLatch.countDown(); - } - }); - - closeAndWaitForRecovery(); - wait(listenerLatch); - expectChannelRecovery(channel); - channel.basicPublish(x, "", null, "msg".getBytes()); - assertDelivered(q, 1); - assertFalse(nameBefore.get().equals(nameAfter.get())); - channel.queueDelete(q); - } - - public void testExchangeToExchangeBindingRecovery() throws IOException, InterruptedException { - String q = channel.queueDeclare("", false, false, false, null).getQueue(); - String x1 = "amq.fanout"; - String x2 = generateExchangeName(); - channel.exchangeDeclare(x2, "fanout"); - channel.exchangeBind(x1, x2, ""); - channel.queueBind(q, x1, ""); - - try { - closeAndWaitForRecovery(); - expectChannelRecovery(channel); - channel.basicPublish(x2, "", null, "msg".getBytes()); - assertDelivered(q, 1); - } finally { - channel.exchangeDelete(x2); - channel.queueDelete(q); - } - } - - public void testThatDeletedQueueBindingsDontReappearOnRecovery() throws IOException, InterruptedException { - String q = channel.queueDeclare("", false, false, false, null).getQueue(); - String x1 = "amq.fanout"; - String x2 = generateExchangeName(); - channel.exchangeDeclare(x2, "fanout"); - channel.exchangeBind(x1, x2, ""); - channel.queueBind(q, x1, ""); - channel.queueUnbind(q, x1, ""); - - try { - closeAndWaitForRecovery(); - expectChannelRecovery(channel); - channel.basicPublish(x2, "", null, "msg".getBytes()); - assertDelivered(q, 0); - } finally { - channel.exchangeDelete(x2); - channel.queueDelete(q); - } - } - - public void testThatDeletedExchangeBindingsDontReappearOnRecovery() throws IOException, InterruptedException { - String q = channel.queueDeclare("", false, false, false, null).getQueue(); - String x1 = "amq.fanout"; - String x2 = generateExchangeName(); - channel.exchangeDeclare(x2, "fanout"); - channel.exchangeBind(x1, x2, ""); - channel.queueBind(q, x1, ""); - channel.exchangeUnbind(x1, x2, ""); - - try { - closeAndWaitForRecovery(); - expectChannelRecovery(channel); - channel.basicPublish(x2, "", null, "msg".getBytes()); - assertDelivered(q, 0); - } finally { - channel.exchangeDelete(x2); - channel.queueDelete(q); - } - } - - public void testThatDeletedExchangeDoesNotReappearOnRecover() throws IOException, InterruptedException { - String x = generateExchangeName(); - channel.exchangeDeclare(x, "fanout"); - channel.exchangeDelete(x); - try { - closeAndWaitForRecovery(); - expectChannelRecovery(channel); - channel.exchangeDeclarePassive(x); - fail("Expected passive declare to fail"); - } catch (IOException ioe) { - // expected - } - } - - public void testThatDeletedQueueDoesNotReappearOnRecover() throws IOException, InterruptedException { - String q = channel.queueDeclare().getQueue(); - channel.queueDelete(q); - try { - closeAndWaitForRecovery(); - expectChannelRecovery(channel); - channel.queueDeclarePassive(q); - fail("Expected passive declare to fail"); - } catch (IOException ioe) { - // expected - } - } - - public void testThatCancelledConsumerDoesNotReappearOnRecover() throws IOException, InterruptedException { - String q = UUID.randomUUID().toString(); - channel.queueDeclare(q, false, false, false, null); - String tag = channel.basicConsume(q, new DefaultConsumer(channel)); - assertConsumerCount(1, q); - channel.basicCancel(tag); - closeAndWaitForRecovery(); - expectChannelRecovery(channel); - assertConsumerCount(0, q); - } - - public void testConsumerRecoveryWithManyConsumers() throws IOException, InterruptedException { - String q = channel.queueDeclare(UUID.randomUUID().toString(), false, false, false, null).getQueue(); - final int n = 1024; - for (int i = 0; i < n; i++) { - channel.basicConsume(q, new DefaultConsumer(channel)); - } - final AtomicReference tagA = new AtomicReference(); - final AtomicReference tagB = new AtomicReference(); - final CountDownLatch listenerLatch = new CountDownLatch(n); - ((AutorecoveringConnection)connection).addConsumerRecoveryListener(new ConsumerRecoveryListener() { - @Override - public void consumerRecovered(String oldConsumerTag, String newConsumerTag) { - tagA.set(oldConsumerTag); - tagB.set(newConsumerTag); - listenerLatch.countDown(); - } - }); - - assertConsumerCount(n, q); - closeAndWaitForRecovery(); - wait(listenerLatch); - assertTrue(tagA.get().equals(tagB.get())); - expectChannelRecovery(channel); - assertConsumerCount(n, q); - - } - - public void testSubsequentRecoveriesWithClientNamedQueue() throws IOException, InterruptedException { - String q = channel.queueDeclare(UUID.randomUUID().toString(), false, false, false, null).getQueue(); - - assertConsumerCount(0, q); - channel.basicConsume(q, new DefaultConsumer(channel)); - - for(int i = 0; i < 10; i++) { - assertConsumerCount(1, q); - closeAndWaitForRecovery(); - } - - channel.queueDelete(q); - } - - public void testQueueRecoveryWithManyQueues() throws IOException, InterruptedException, TimeoutException { - List qs = new ArrayList(); - final int n = 1024; - for (int i = 0; i < n; i++) { - qs.add(channel.queueDeclare(UUID.randomUUID().toString(), true, false, false, null).getQueue()); - } - closeAndWaitForRecovery(); - expectChannelRecovery(channel); - for(String q : qs) { - expectQueueRecovery(channel, q); - channel.queueDelete(q); - } - } - - public void testChannelRecoveryCallback() throws IOException, InterruptedException { - final CountDownLatch latch = new CountDownLatch(2); - final RecoveryListener listener = new RecoveryListener() { - public void handleRecovery(Recoverable recoverable) { - latch.countDown(); - } - }; - AutorecoveringChannel ch1 = (AutorecoveringChannel) connection.createChannel(); - ch1.addRecoveryListener(listener); - AutorecoveringChannel ch2 = (AutorecoveringChannel) connection.createChannel(); - ch2.addRecoveryListener(listener); - - assertTrue(ch1.isOpen()); - assertTrue(ch2.isOpen()); - closeAndWaitForRecovery(); - expectChannelRecovery(ch1); - expectChannelRecovery(ch2); - wait(latch); - } - - public void testBasicAckAfterChannelRecovery() throws IOException, InterruptedException { - final AtomicInteger consumed = new AtomicInteger(0); - int n = 5; - final CountDownLatch latch = new CountDownLatch(n); - Consumer consumer = new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, - Envelope envelope, - AMQP.BasicProperties properties, - byte[] body) throws IOException { - try { - if (consumed.intValue() > 0 && consumed.intValue() % 4 == 0) { - CountDownLatch recoveryLatch = prepareForRecovery(connection); - Host.closeConnection((AutorecoveringConnection)connection); - ConnectionRecovery.wait(recoveryLatch); - } - channel.basicAck(envelope.getDeliveryTag(), false); - } catch (InterruptedException e) { - // ignore - } finally { - consumed.incrementAndGet(); - latch.countDown(); - } - } - }; - - String q = channel.queueDeclare().getQueue(); - channel.basicConsume(q, consumer); - AutorecoveringConnection publishingConnection = newRecoveringConnection(false); - Channel publishingChannel = publishingConnection.createChannel(); - for (int i = 0; i < n; i++) { - publishingChannel.basicPublish("", q, null, "msg".getBytes()); - } - wait(latch); - publishingConnection.abort(); - } - - private void assertConsumerCount(int exp, String q) throws IOException { - assertEquals(exp, channel.queueDeclarePassive(q).getConsumerCount()); - } - - private AMQP.Queue.DeclareOk declareClientNamedQueue(Channel ch, String q) throws IOException { - return ch.queueDeclare(q, true, false, false, null); - } - - private AMQP.Queue.DeclareOk declareClientNamedAutoDeleteQueue(Channel ch, String q) throws IOException { - return ch.queueDeclare(q, true, false, true, null); - } - - - private void declareClientNamedQueueNoWait(Channel ch, String q) throws IOException { - ch.queueDeclareNoWait(q, true, false, false, null); - } - - private AMQP.Exchange.DeclareOk declareExchange(Channel ch, String x) throws IOException { - return ch.exchangeDeclare(x, "fanout", false); - } - - private void declareExchangeNoWait(Channel ch, String x) throws IOException { - ch.exchangeDeclareNoWait(x, "fanout", false, false, false, null); - } - - private void expectQueueRecovery(Channel ch, String q) throws IOException, InterruptedException, TimeoutException { - ch.confirmSelect(); - ch.queuePurge(q); - AMQP.Queue.DeclareOk ok1 = declareClientNamedQueue(ch, q); - assertEquals(0, ok1.getMessageCount()); - ch.basicPublish("", q, null, "msg".getBytes()); - waitForConfirms(ch); - AMQP.Queue.DeclareOk ok2 = declareClientNamedQueue(ch, q); - assertEquals(1, ok2.getMessageCount()); - } - - private void expectAutoDeleteQueueAndBindingRecovery(Channel ch, String x, String q) throws IOException, InterruptedException, - TimeoutException { - ch.confirmSelect(); - ch.queuePurge(q); - AMQP.Queue.DeclareOk ok1 = declareClientNamedAutoDeleteQueue(ch, q); - assertEquals(0, ok1.getMessageCount()); - ch.exchangeDeclare(x, "fanout"); - ch.basicPublish(x, "", null, "msg".getBytes()); - waitForConfirms(ch); - AMQP.Queue.DeclareOk ok2 = declareClientNamedAutoDeleteQueue(ch, q); - assertEquals(1, ok2.getMessageCount()); - } - - private void expectExchangeRecovery(Channel ch, String x) throws IOException, InterruptedException, TimeoutException { - ch.confirmSelect(); - String q = ch.queueDeclare().getQueue(); - final String rk = "routing-key"; - ch.queueBind(q, x, rk); - ch.basicPublish(x, rk, null, "msg".getBytes()); - waitForConfirms(ch); - ch.exchangeDeclarePassive(x); - } - - private CountDownLatch prepareForRecovery(Connection conn) { - final CountDownLatch latch = new CountDownLatch(1); - ((AutorecoveringConnection)conn).addRecoveryListener(new RecoveryListener() { - public void handleRecovery(Recoverable recoverable) { - latch.countDown(); - } - }); - return latch; - } - - private CountDownLatch prepareForShutdown(Connection conn) throws InterruptedException { - final CountDownLatch latch = new CountDownLatch(1); - conn.addShutdownListener(new ShutdownListener() { - public void shutdownCompleted(ShutdownSignalException cause) { - latch.countDown(); - } - }); - return latch; - } - - private void closeAndWaitForRecovery() throws IOException, InterruptedException { - closeAndWaitForRecovery((AutorecoveringConnection)this.connection); - } - - private void closeAndWaitForRecovery(AutorecoveringConnection connection) throws IOException, InterruptedException { - CountDownLatch latch = prepareForRecovery(connection); - Host.closeConnection(connection); - wait(latch); - } - - private void restartPrimaryAndWaitForRecovery() throws IOException, InterruptedException { - restartPrimaryAndWaitForRecovery(this.connection); - } - - private void restartPrimaryAndWaitForRecovery(Connection connection) throws IOException, InterruptedException { - CountDownLatch latch = prepareForRecovery(connection); - // restart without tearing down and setting up - // new connection and channel - bareRestart(); - wait(latch); - } - - private void expectChannelRecovery(Channel ch) throws InterruptedException { - assertTrue(ch.isOpen()); - } - - @Override - protected ConnectionFactory newConnectionFactory() { - return buildConnectionFactoryWithRecoveryEnabled(false); - } - - private AutorecoveringConnection newRecoveringConnection(boolean disableTopologyRecovery) throws IOException { - ConnectionFactory cf = buildConnectionFactoryWithRecoveryEnabled(disableTopologyRecovery); - return (AutorecoveringConnection) cf.newConnection(); - } - - private AutorecoveringConnection newRecoveringConnection(Address[] addresses) throws IOException { - return newRecoveringConnection(false, addresses); - } - - private AutorecoveringConnection newRecoveringConnection(boolean disableTopologyRecovery, Address[] addresses) throws IOException { - ConnectionFactory cf = buildConnectionFactoryWithRecoveryEnabled(disableTopologyRecovery); - return (AutorecoveringConnection) cf.newConnection(addresses); - } - - private ConnectionFactory buildConnectionFactoryWithRecoveryEnabled(boolean disableTopologyRecovery) { - ConnectionFactory cf = new ConnectionFactory(); - cf.setNetworkRecoveryInterval(RECOVERY_INTERVAL); - cf.setAutomaticRecoveryEnabled(true); - if (disableTopologyRecovery) { - cf.setTopologyRecoveryEnabled(false); - } - return cf; - } - - private static void wait(CountDownLatch latch) throws InterruptedException { - // Very very generous amount of time to wait, just make sure we never - // hang forever - assertTrue(latch.await(1800, TimeUnit.SECONDS)); - } - - private void waitForConfirms(Channel ch) throws InterruptedException, TimeoutException { - ch.waitForConfirms(30 * 60 * 1000); - } - - protected void assertRecordedQueues(Connection conn, int size) { - assertEquals(size, ((AutorecoveringConnection)conn).getRecordedQueues().size()); - } - - protected void assertRecordedExchanges(Connection conn, int size) { - assertEquals(size, ((AutorecoveringConnection)conn).getRecordedExchanges().size()); - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/ConsumerPriorities.java b/test/src/com/rabbitmq/client/test/functional/ConsumerPriorities.java deleted file mode 100644 index c1b2841c67..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/ConsumerPriorities.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.QueueingConsumer; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -public class ConsumerPriorities extends BrokerTestCase { - public void testValidation() throws IOException { - assertFailValidation(args("banana")); - assertFailValidation(args(new HashMap())); - assertFailValidation(args(null)); - assertFailValidation(args(Arrays.asList(1, 2, 3))); - } - - private void assertFailValidation(Map args) throws IOException { - Channel ch = connection.createChannel(); - String queue = ch.queueDeclare().getQueue(); - try { - ch.basicConsume(queue, true, args, new QueueingConsumer(ch)); - fail("Validation should fail for " + args); - } catch (IOException ioe) { - checkShutdownSignal(AMQP.PRECONDITION_FAILED, ioe); - } - } - - private static final int COUNT = 10; - - public void testConsumerPriorities() throws Exception { - String queue = channel.queueDeclare().getQueue(); - QueueingConsumer highConsumer = new QueueingConsumer(channel); - QueueingConsumer medConsumer = new QueueingConsumer(channel); - QueueingConsumer lowConsumer = new QueueingConsumer(channel); - String high = channel.basicConsume(queue, true, args(1), highConsumer); - String med = channel.basicConsume(queue, true, medConsumer); - channel.basicConsume(queue, true, args(-1), lowConsumer); - - publish(queue, COUNT, "high"); - channel.basicCancel(high); - publish(queue, COUNT, "med"); - channel.basicCancel(med); - publish(queue, COUNT, "low"); - - assertContents(highConsumer, COUNT, "high"); - assertContents(medConsumer, COUNT, "med"); - assertContents(lowConsumer, COUNT, "low"); - } - - private Map args(Object o) { - Map map = new HashMap(); - map.put("x-priority", o); - return map; - } - - private void assertContents(QueueingConsumer qc, int count, String msg) throws InterruptedException { - for (int i = 0; i < count; i++) { - QueueingConsumer.Delivery d = qc.nextDelivery(); - assertEquals(msg, new String(d.getBody())); - } - assertEquals(null, qc.nextDelivery(0)); - } - - private void publish(String queue, int count, String msg) throws IOException { - for (int i = 0; i < count; i++) { - channel.basicPublish("", queue, MessageProperties.MINIMAL_BASIC, msg.getBytes()); - } - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/DeadLetterExchange.java b/test/src/com/rabbitmq/client/test/functional/DeadLetterExchange.java deleted file mode 100644 index 77e1693f46..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/DeadLetterExchange.java +++ /dev/null @@ -1,615 +0,0 @@ -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.AMQP.BasicProperties; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.QueueingConsumer; -import com.rabbitmq.client.QueueingConsumer.Delivery; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; -import java.util.Arrays; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -public class DeadLetterExchange extends BrokerTestCase { - public static final String DLX = "dead.letter.exchange"; - public static final String DLX_ARG = "x-dead-letter-exchange"; - public static final String DLX_RK_ARG = "x-dead-letter-routing-key"; - public static final String TEST_QUEUE_NAME = "test.queue.dead.letter"; - public static final String DLQ = "queue.dlq"; - public static final String DLQ2 = "queue.dlq2"; - public static final int MSG_COUNT = 10; - public static final int TTL = 1000; - - @Override - protected void createResources() throws IOException { - channel.exchangeDeclare(DLX, "direct"); - channel.queueDeclare(DLQ, false, true, false, null); - } - - @Override - protected void releaseResources() throws IOException { - channel.exchangeDelete(DLX); - } - - public void testDeclareQueueWithExistingDeadLetterExchange() - throws IOException - { - declareQueue(DLX); - } - - public void testDeclareQueueWithNonExistingDeadLetterExchange() - throws IOException - { - declareQueue("some.random.exchange.name"); - } - - public void testDeclareQueueWithEquivalentDeadLetterExchange() - throws IOException - { - declareQueue(DLX); - declareQueue(DLX); - } - - public void testDeclareQueueWithEquivalentDeadLetterRoutingKey() - throws IOException - { - declareQueue(TEST_QUEUE_NAME, DLX, "routing_key", null); - declareQueue(TEST_QUEUE_NAME, DLX, "routing_key", null); - } - - public void testDeclareQueueWithInvalidDeadLetterExchangeArg() - throws IOException - { - try { - declareQueue(133); - fail("x-dead-letter-exchange must be a valid exchange name"); - } catch (IOException ex) { - checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); - } - } - - public void testRedeclareQueueWithInvalidDeadLetterExchangeArg() - throws IOException - { - declareQueue("inequivalent_dlx_name", "dlx_foo", null, null); - try { - declareQueue("inequivalent_dlx_name", "dlx_bar", null, null); - fail("x-dead-letter-exchange must be a valid exchange name " + - "and must not change in subsequent declarations"); - } catch (IOException ex) { - checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); - } - } - - public void testDeclareQueueWithInvalidDeadLetterRoutingKeyArg() - throws IOException - { - try { - declareQueue("foo", "amq.direct", 144, null); - fail("x-dead-letter-routing-key must be a string"); - } catch (IOException ex) { - checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); - } - } - - public void testRedeclareQueueWithInvalidDeadLetterRoutingKeyArg() - throws IOException - { - declareQueue("inequivalent_dlx_rk", "amq.direct", "dlx_rk", null); - try { - declareQueue("inequivalent_dlx_rk", "amq.direct", "dlx_rk2", null); - fail("x-dead-letter-routing-key must be a string and must not " + - "change in subsequent declarations"); - } catch (IOException ex) { - checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); - } - } - - public void testDeclareQueueWithRoutingKeyButNoDeadLetterExchange() - throws IOException - { - try { - Map args = new HashMap(); - args.put(DLX_RK_ARG, "foo"); - - channel.queueDeclare("bar", false, true, false, args); - fail("dlx must be defined if dl-rk is set"); - } catch (IOException ex) { - checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); - } - } - - public void testRedeclareQueueWithRoutingKeyButNoDeadLetterExchange() - throws IOException - { - try { - Map args = new HashMap(); - channel.queueDeclare("bar", false, true, false, args); - - args.put(DLX_RK_ARG, "foo"); - - channel.queueDeclare("bar", false, true, false, args); - fail("x-dead-letter-exchange must be specified if " + - "x-dead-letter-routing-key is set"); - } catch (IOException ex) { - checkShutdownSignal(AMQP.PRECONDITION_FAILED, ex); - } - } - - public void testDeadLetterQueueTTLExpiredMessages() throws Exception { - ttlTest(TTL); - } - - public void testDeadLetterQueueZeroTTLExpiredMessages() throws Exception { - ttlTest(0); - } - - public void testDeadLetterQueueTTLPromptExpiry() throws Exception { - Map args = new HashMap(); - args.put("x-message-ttl", TTL); - declareQueue(TEST_QUEUE_NAME, DLX, null, args); - channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); - channel.queueBind(DLQ, DLX, "test"); - - //measure round-trip latency - QueueingConsumer c = new QueueingConsumer(channel); - String cTag = channel.basicConsume(TEST_QUEUE_NAME, true, c); - long start = System.currentTimeMillis(); - publish(null, "test"); - Delivery d = c.nextDelivery(TTL); - long stop = System.currentTimeMillis(); - assertNotNull(d); - channel.basicCancel(cTag); - long latency = stop-start; - - channel.basicConsume(DLQ, true, c); - - // publish messages at regular intervals until currentTime + - // 3/4th of TTL - int count = 0; - start = System.currentTimeMillis(); - stop = start + TTL * 3 / 4; - long now = start; - while (now < stop) { - publish(null, Long.toString(now)); - count++; - Thread.sleep(TTL / 100); - now = System.currentTimeMillis(); - } - - checkPromptArrival(c, count, latency); - - start = System.currentTimeMillis(); - // publish message - which kicks off the queue's ttl timer - - // and immediately fetch it in noack mode - publishAt(start); - basicGet(TEST_QUEUE_NAME); - // publish a 2nd message and immediately fetch it in ack mode - publishAt(start + TTL * 1 / 2); - GetResponse r = channel.basicGet(TEST_QUEUE_NAME, false); - // publish a 3rd message - publishAt(start + TTL * 3 / 4); - // reject 2nd message after the initial timer has fired but - // before the message is due to expire - waitUntil(start + TTL * 5 / 4); - channel.basicReject(r.getEnvelope().getDeliveryTag(), true); - - checkPromptArrival(c, 2, latency); - } - - public void testDeadLetterDeletedDLX() throws Exception { - declareQueue(TEST_QUEUE_NAME, DLX, null, null, 1); - channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); - channel.queueBind(DLQ, DLX, "test"); - - channel.exchangeDelete(DLX); - publishN(MSG_COUNT); - sleep(100); - - consumeN(DLQ, 0, WithResponse.NULL); - - channel.exchangeDeclare(DLX, "direct"); - channel.queueBind(DLQ, DLX, "test"); - - publishN(MSG_COUNT); - sleep(100); - - consumeN(DLQ, MSG_COUNT, WithResponse.NULL); - } - - public void testDeadLetterPerMessageTTLRemoved() throws Exception { - declareQueue(TEST_QUEUE_NAME, DLX, null, null, 1); - channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); - channel.queueBind(DLQ, DLX, "test"); - - final BasicProperties props = MessageProperties.BASIC.builder().expiration("100").build(); - publish(props, "test message"); - - // The message's expiration property should have been removed, thus - // after 100ms of hitting the queue, the message should get routed to - // the DLQ *AND* should remain there, not getting removed after a subsequent - // wait time > 100ms - sleep(500); - consumeN(DLQ, 1, new WithResponse() { - @SuppressWarnings("unchecked") - public void process(GetResponse getResponse) { - assertNull(getResponse.getProps().getExpiration()); - Map headers = getResponse.getProps().getHeaders(); - assertNotNull(headers); - ArrayList death = (ArrayList)headers.get("x-death"); - assertNotNull(death); - assertDeathReason(death, 0, TEST_QUEUE_NAME, "expired"); - final Map deathHeader = - (Map)death.get(0); - assertEquals("100", deathHeader.get("original-expiration").toString()); - } - }); - } - - public void testDeadLetterOnReject() throws Exception { - rejectionTest(false); - } - - public void testDeadLetterOnNack() throws Exception { - rejectionTest(true); - } - - public void testDeadLetterNoDeadLetterQueue() throws IOException { - channel.queueDelete(DLQ); - - declareQueue(TEST_QUEUE_NAME, DLX, null, null, 1); - channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); - - publishN(MSG_COUNT); - } - - public void testDeadLetterMultipleDeadLetterQueues() - throws IOException - { - declareQueue(TEST_QUEUE_NAME, DLX, null, null, 1); - - channel.queueDeclare(DLQ2, false, true, false, null); - - channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); - channel.queueBind(DLQ, DLX, "test"); - channel.queueBind(DLQ2, DLX, "test"); - - publishN(MSG_COUNT); - } - - public void testDeadLetterTwice() throws Exception { - declareQueue(TEST_QUEUE_NAME, DLX, null, null, 1); - - channel.queueDelete(DLQ); - declareQueue(DLQ, DLX, null, null, 1); - - channel.queueDeclare(DLQ2, false, true, false, null); - - channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); - channel.queueBind(DLQ, DLX, "test"); - channel.queueBind(DLQ2, DLX, "test"); - - publishN(MSG_COUNT); - - sleep(100); - - // There should now be two copies of each message on DLQ2: one - // with one set of death headers, and another with two sets. - consumeN(DLQ2, MSG_COUNT*2, new WithResponse() { - @SuppressWarnings("unchecked") - public void process(GetResponse getResponse) { - Map headers = getResponse.getProps().getHeaders(); - assertNotNull(headers); - ArrayList death = (ArrayList)headers.get("x-death"); - assertNotNull(death); - if (death.size() == 1) { - assertDeathReason(death, 0, TEST_QUEUE_NAME, "expired"); - } else if (death.size() == 2) { - assertDeathReason(death, 0, DLQ, "expired"); - assertDeathReason(death, 1, TEST_QUEUE_NAME, "expired"); - } else { - fail("message was dead-lettered more times than expected"); - } - } - }); - } - - public void testDeadLetterSelf() throws Exception { - declareQueue(TEST_QUEUE_NAME, "amq.direct", "test", null, 1); - channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); - - publishN(MSG_COUNT); - // This test hangs if the queue doesn't process ALL the - // messages before being deleted, so make sure the next - // sleep is long enough. - sleep(200); - - // The messages will NOT be dead-lettered to self. - consumeN(TEST_QUEUE_NAME, 0, WithResponse.NULL); - } - - public void testDeadLetterCycle() throws Exception { - // testDeadLetterTwice and testDeadLetterSelf both test that we drop - // messages in pure-expiry cycles. So we just need to test that - // non-pure-expiry cycles do not drop messages. - - declareQueue("queue1", "", "queue2", null, 1); - declareQueue("queue2", "", "queue1", null, 0); - - channel.basicPublish("", "queue1", MessageProperties.BASIC, "".getBytes()); - final CountDownLatch latch = new CountDownLatch(10); - channel.basicConsume("queue2", false, - new DefaultConsumer(channel) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, - AMQP.BasicProperties properties, byte[] body) throws IOException { - channel.basicReject(envelope.getDeliveryTag(), false); - latch.countDown(); - } - }); - assertTrue(latch.await(10, TimeUnit.SECONDS)); - } - - public void testDeadLetterNewRK() throws Exception { - declareQueue(TEST_QUEUE_NAME, DLX, "test-other", null, 1); - - channel.queueDeclare(DLQ2, false, true, false, null); - - channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); - channel.queueBind(DLQ, DLX, "test"); - channel.queueBind(DLQ2, DLX, "test-other"); - - Map headers = new HashMap(); - headers.put("CC", Arrays.asList("foo")); - headers.put("BCC", Arrays.asList("bar")); - - publishN(MSG_COUNT, (new AMQP.BasicProperties.Builder()) - .headers(headers) - .build()); - - sleep(100); - - consumeN(DLQ, 0, WithResponse.NULL); - consumeN(DLQ2, MSG_COUNT, new WithResponse() { - @SuppressWarnings("unchecked") - public void process(GetResponse getResponse) { - Map headers = getResponse.getProps().getHeaders(); - assertNotNull(headers); - assertNull(headers.get("CC")); - assertNull(headers.get("BCC")); - - ArrayList death = (ArrayList)headers.get("x-death"); - assertNotNull(death); - assertEquals(1, death.size()); - assertDeathReason(death, 0, TEST_QUEUE_NAME, - "expired", "amq.direct", - Arrays.asList("test", "foo")); - } - }); - } - - public void rejectionTest(final boolean useNack) throws Exception { - deadLetterTest(new Callable() { - public Void call() throws Exception { - for (int x = 0; x < MSG_COUNT; x++) { - GetResponse getResponse = - channel.basicGet(TEST_QUEUE_NAME, false); - long tag = getResponse.getEnvelope().getDeliveryTag(); - if (useNack) { - channel.basicNack(tag, false, false); - } else { - channel.basicReject(tag, false); - } - } - return null; - } - }, null, "rejected"); - } - - private void deadLetterTest(final Runnable deathTrigger, - Map queueDeclareArgs, - String reason) - throws Exception - { - deadLetterTest(new Callable() { - public Object call() throws Exception { - deathTrigger.run(); - return null; - } - }, queueDeclareArgs, reason); - } - - private void deadLetterTest(Callable deathTrigger, - Map queueDeclareArgs, - final String reason) - throws Exception - { - declareQueue(TEST_QUEUE_NAME, DLX, null, queueDeclareArgs); - - channel.queueBind(TEST_QUEUE_NAME, "amq.direct", "test"); - channel.queueBind(DLQ, DLX, "test"); - - publishN(MSG_COUNT); - - deathTrigger.call(); - - consume(channel, reason); - } - - public static void consume(final Channel channel, final String reason) throws IOException { - consumeN(channel, DLQ, MSG_COUNT, new WithResponse() { - @SuppressWarnings("unchecked") - public void process(GetResponse getResponse) { - Map headers = getResponse.getProps().getHeaders(); - assertNotNull(headers); - ArrayList death = (ArrayList) headers.get("x-death"); - assertNotNull(death); - assertEquals(1, death.size()); - assertDeathReason(death, 0, TEST_QUEUE_NAME, reason, - "amq.direct", - Arrays.asList("test")); - } - }); - } - - private void ttlTest(final long ttl) throws Exception { - Map args = new HashMap(); - args.put("x-message-ttl", ttl); - deadLetterTest(new Runnable() { - public void run() { sleep(ttl + 1500); } - }, args, "expired"); - } - - private void sleep(long millis) { - try { - Thread.sleep(millis); - } catch (InterruptedException ex) { - // whoosh - } - } - - /* check that each message arrives within epsilon of the - publication time + TTL + latency */ - private void checkPromptArrival(QueueingConsumer c, - int count, long latency) throws Exception { - long epsilon = TTL / 50; - for (int i = 0; i < count; i++) { - Delivery d = c.nextDelivery(TTL + TTL + latency + epsilon); - assertNotNull("message #" + i + " did not expire", d); - long now = System.currentTimeMillis(); - long publishTime = Long.valueOf(new String(d.getBody())); - long targetTime = publishTime + TTL + latency; - assertTrue("expiry outside bounds (+/- " + epsilon + "): " + - (now - targetTime), - (now >= targetTime - epsilon) && - (now <= targetTime + epsilon)); - } - } - - private void declareQueue(Object deadLetterExchange) throws IOException { - declareQueue(TEST_QUEUE_NAME, deadLetterExchange, null, null); - } - - private void declareQueue(String queue, Object deadLetterExchange, - Object deadLetterRoutingKey, - Map args) throws IOException { - declareQueue(queue, deadLetterExchange, deadLetterRoutingKey, args, 0); - } - - private void declareQueue(String queue, Object deadLetterExchange, - Object deadLetterRoutingKey, - Map args, int ttl) - throws IOException - { - if (args == null) { - args = new HashMap(); - } - - if (ttl > 0){ - args.put("x-message-ttl", ttl); - } - - args.put(DLX_ARG, deadLetterExchange); - if (deadLetterRoutingKey != null) { - args.put(DLX_RK_ARG, deadLetterRoutingKey); - } - channel.queueDeclare(queue, false, true, false, args); - } - - private void publishN(int n) throws IOException { - publishN(n, null); - } - - private void publishN(int n, AMQP.BasicProperties props) - throws IOException - { - for(int x = 0; x < n; x++) { publish(props, "test message"); } - } - - private void publish(AMQP.BasicProperties props, String body) - throws IOException - { - channel.basicPublish("amq.direct", "test", props, body.getBytes()); - } - - private void publishAt(long when) throws Exception { - waitUntil(when); - publish(null, Long.toString(System.currentTimeMillis())); - } - - private void waitUntil(long when) throws Exception { - long delay = when - System.currentTimeMillis(); - Thread.sleep(delay > 0 ? delay : 0); - } - - private void consumeN(String queue, int n, WithResponse withResponse) - throws IOException - { - consumeN(channel, queue, n, withResponse); - } - - private static void consumeN(Channel channel, String queue, int n, WithResponse withResponse) - throws IOException - { - for(int x = 0; x < n; x++) { - GetResponse getResponse = - channel.basicGet(queue, true); - assertNotNull("Messages not dead-lettered (" + (n-x) + " left)", - getResponse); - assertEquals("test message", new String(getResponse.getBody())); - withResponse.process(getResponse); - } - GetResponse getResponse = channel.basicGet(queue, true); - assertNull("expected empty queue", getResponse); - } - - @SuppressWarnings("unchecked") - private static void assertDeathReason(List death, int num, - String queue, String reason, - String exchange, List routingKeys) - { - Map deathHeader = - (Map)death.get(num); - assertEquals(exchange, deathHeader.get("exchange").toString()); - - List deathRKs = new ArrayList(); - for (Object rk : (ArrayList)deathHeader.get("routing-keys")) { - deathRKs.add(rk.toString()); - } - Collections.sort(deathRKs); - Collections.sort(routingKeys); - assertEquals(routingKeys, deathRKs); - - assertDeathReason(death, num, queue, reason); - } - - @SuppressWarnings("unchecked") - private static void assertDeathReason(List death, int num, - String queue, String reason) { - Map deathHeader = - (Map)death.get(num); - assertEquals(queue, deathHeader.get("queue").toString()); - assertEquals(reason, deathHeader.get("reason").toString()); - } - - private static interface WithResponse { - static final WithResponse NULL = new WithResponse() { - public void process(GetResponse getResponse) { - } - }; - - public void process(GetResponse response); - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/DirectReplyTo.java b/test/src/com/rabbitmq/client/test/functional/DirectReplyTo.java deleted file mode 100644 index 0b859d801c..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/DirectReplyTo.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.QueueingConsumer; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; - -public class DirectReplyTo extends BrokerTestCase { - private static final String QUEUE = "amq.rabbitmq.reply-to"; - - public void testRoundTrip() throws IOException, InterruptedException { - QueueingConsumer c = new QueueingConsumer(channel); - String replyTo = rpcFirstHalf(c); - declare(connection, replyTo, true); - channel.confirmSelect(); - basicPublishVolatile("response".getBytes(), "", replyTo, MessageProperties.BASIC); - channel.waitForConfirms(); - - QueueingConsumer.Delivery del = c.nextDelivery(); - assertEquals("response", new String(del.getBody())); - } - - public void testHack() throws IOException, InterruptedException { - QueueingConsumer c = new QueueingConsumer(channel); - String replyTo = rpcFirstHalf(c); - // 5 chars should overwrite part of the key but not the pid; aiming to prove - // we can't publish using just the pid - replyTo = replyTo.substring(0, replyTo.length() - 5) + "xxxxx"; - declare(connection, replyTo, false); - basicPublishVolatile("response".getBytes(), "", replyTo, MessageProperties.BASIC); - - QueueingConsumer.Delivery del = c.nextDelivery(500); - assertNull(del); - } - - private void declare(Connection connection, String q, boolean expectedExists) throws IOException { - Channel ch = connection.createChannel(); - try { - ch.queueDeclarePassive(q); - assertTrue(expectedExists); - } catch (IOException e) { - assertFalse(expectedExists); - checkShutdownSignal(AMQP.NOT_FOUND, e); - // Hmmm... - channel = connection.createChannel(); - } - } - - public void testConsumeFail() throws IOException, InterruptedException { - QueueingConsumer c = new QueueingConsumer(channel); - Channel ch = connection.createChannel(); - try { - ch.basicConsume(QUEUE, false, c); - } catch (IOException e) { - // Can't have ack mode - checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); - } - - ch = connection.createChannel(); - ch.basicConsume(QUEUE, true, c); - try { - ch.basicConsume(QUEUE, true, c); - } catch (IOException e) { - // Can't have multiple consumers - checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); - } - } - - public void testConsumeSuccess() throws IOException, InterruptedException { - QueueingConsumer c = new QueueingConsumer(channel); - String ctag = channel.basicConsume(QUEUE, true, c); - channel.basicCancel(ctag); - - String ctag2 = channel.basicConsume(QUEUE, true, c); - channel.basicCancel(ctag2); - assertNotSame(ctag, ctag2); - } - - private String rpcFirstHalf(QueueingConsumer c) throws IOException { - channel.basicConsume(QUEUE, true, c); - String serverQueue = channel.queueDeclare().getQueue(); - basicPublishVolatile("request".getBytes(), "", serverQueue, props()); - - GetResponse req = channel.basicGet(serverQueue, true); - return req.getProps().getReplyTo(); - } - - private AMQP.BasicProperties props() { - return MessageProperties.BASIC.builder().replyTo(QUEUE).build(); - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/DoubleDeletion.java b/test/src/com/rabbitmq/client/test/functional/DoubleDeletion.java deleted file mode 100644 index 7ba5e3837e..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/DoubleDeletion.java +++ /dev/null @@ -1,46 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.functional; - -import java.io.IOException; - -import com.rabbitmq.client.test.BrokerTestCase; - -public class DoubleDeletion extends BrokerTestCase -{ - protected static final String Q = "DoubleDeletionQueue"; - protected static final String X = "DoubleDeletionExchange"; - - public void testDoubleDeletionQueue() - throws IOException - { - channel.queueDelete(Q); - channel.queueDeclare(Q, false, false, false, null); - channel.queueDelete(Q); - channel.queueDelete(Q); - } - - public void testDoubleDeletionExchange() - throws IOException - { - channel.exchangeDelete(X); - channel.exchangeDeclare(X, "direct"); - channel.exchangeDelete(X); - channel.exchangeDelete(X); - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/DurableOnTransient.java b/test/src/com/rabbitmq/client/test/functional/DurableOnTransient.java deleted file mode 100644 index 48c359ccc7..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/DurableOnTransient.java +++ /dev/null @@ -1,85 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.functional; - -import java.io.IOException; - -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.MessageProperties; - -public class DurableOnTransient extends ClusteredTestBase -{ - protected static final String Q = "DurableQueue"; - protected static final String X = "TransientExchange"; - - private GetResponse basicGet() - throws IOException - { - return channel.basicGet(Q, true); - } - - private void basicPublish() - throws IOException - { - channel.basicPublish(X, "", - MessageProperties.PERSISTENT_TEXT_PLAIN, - "persistent message".getBytes()); - } - - protected void createResources() throws IOException { - // Transient exchange - channel.exchangeDeclare(X, "direct", false); - // durable queue - channel.queueDeclare(Q, true, false, false, null); - } - - protected void releaseResources() throws IOException { - channel.queueDelete(Q); - channel.exchangeDelete(X); - } - - public void testBindDurableToTransient() - throws IOException - { - channel.queueBind(Q, X, ""); - basicPublish(); - assertNotNull(basicGet()); - } - - public void testSemiDurableBindingRemoval() throws IOException { - if (clusteredConnection != null) { - declareTransientTopicExchange("x"); - clusteredChannel.queueDeclare("q", true, false, false, null); - channel.queueBind("q", "x", "k"); - - stopSecondary(); - - deleteExchange("x"); - - startSecondary(); - - declareTransientTopicExchange("x"); - - basicPublishVolatile("x", "k"); - assertDelivered("q", 0); - - deleteQueue("q"); - deleteExchange("x"); - } - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/ExceptionHandling.java b/test/src/com/rabbitmq/client/test/functional/ExceptionHandling.java deleted file mode 100644 index d723415c79..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/ExceptionHandling.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Consumer; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.ExceptionHandler; -import com.rabbitmq.client.impl.DefaultExceptionHandler; -import junit.framework.TestCase; - -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -public class ExceptionHandling extends TestCase { - private ConnectionFactory newConnectionFactory(ExceptionHandler eh) { - ConnectionFactory cf = new ConnectionFactory(); - cf.setExceptionHandler(eh); - return cf; - } - - public void testHandleConsumerException() throws IOException, InterruptedException { - final CountDownLatch latch = new CountDownLatch(1); - final DefaultExceptionHandler eh = new DefaultExceptionHandler() { - @Override - public void handleConsumerException(Channel channel, Throwable exception, Consumer consumer, String consumerTag, String methodName) { - latch.countDown(); - } - }; - ConnectionFactory cf = newConnectionFactory(eh); - assertEquals(cf.getExceptionHandler(), eh); - Connection conn = cf.newConnection(); - assertEquals(conn.getExceptionHandler(), eh); - Channel ch = conn.createChannel(); - String q = ch.queueDeclare().getQueue(); - ch.basicConsume(q, new DefaultConsumer(ch) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { - throw new RuntimeException("oops"); - } - }); - ch.basicPublish("", q, null, "".getBytes()); - wait(latch); - } - - public void testNullExceptionHandler() { - ConnectionFactory cf = new ConnectionFactory(); - try { - cf.setExceptionHandler(null); - fail("expected setExceptionHandler to throw"); - } catch (IllegalArgumentException iae) { - // expected - } - } - - private void wait(CountDownLatch latch) throws InterruptedException { - latch.await(1800, TimeUnit.SECONDS); - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/ExceptionMessages.java b/test/src/com/rabbitmq/client/test/functional/ExceptionMessages.java deleted file mode 100644 index d7af4461af..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/ExceptionMessages.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.AlreadyClosedException; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; -import java.util.UUID; - -public class ExceptionMessages extends BrokerTestCase { - public void testAlreadyClosedExceptionMessageWithChannelError() throws IOException { - String uuid = UUID.randomUUID().toString(); - try { - channel.queueDeclarePassive(uuid); - fail("expected queueDeclarePassive to throw"); - } catch (IOException e) { - // ignored - } - - try { - channel.queueDeclarePassive(uuid); - fail("expected queueDeclarePassive to throw"); - } catch (AlreadyClosedException ace) { - assertTrue(ace.getMessage().startsWith("channel is already closed due to channel error")); - } - } - - public void testAlreadyClosedExceptionMessageWithCleanClose() throws IOException { - String uuid = UUID.randomUUID().toString(); - - try { - channel.close(); - channel.queueDeclare(uuid, false, false, false, null); - } catch (AlreadyClosedException ace) { - assertTrue(ace.getMessage().startsWith("channel is already closed due to clean channel shutdown")); - } - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/ExchangeDeclare.java b/test/src/com/rabbitmq/client/test/functional/ExchangeDeclare.java deleted file mode 100644 index ae84ca804e..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/ExchangeDeclare.java +++ /dev/null @@ -1,60 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.functional; - -import java.util.Map; -import java.util.HashMap; -import java.io.IOException; - -public class ExchangeDeclare extends ExchangeEquivalenceBase { - - static final String TYPE = "direct"; - - static final String NAME = "exchange_test"; - - public void releaseResources() throws IOException { - channel.exchangeDelete(NAME); - } - - public void testExchangeNoArgsEquivalence() throws IOException { - channel.exchangeDeclare(NAME, TYPE, false, false, null); - verifyEquivalent(NAME, TYPE, false, false, null); - } - - public void testExchangeNonsenseArgsEquivalent() throws IOException { - channel.exchangeDeclare(NAME, TYPE, false, false, null); - Map args = new HashMap(); - args.put("nonsensical-argument-surely-not-in-use", "foo"); - verifyEquivalent(NAME, TYPE, false, false, args); - } - - public void testExchangeDurableNotEquivalent() throws IOException { - channel.exchangeDeclare(NAME, TYPE, false, false, null); - verifyNotEquivalent(NAME, TYPE, true, false, null); - } - - public void testExchangeTypeNotEquivalent() throws IOException { - channel.exchangeDeclare(NAME, "direct", false, false, null); - verifyNotEquivalent(NAME, "fanout", false, false, null); - } - - public void testExchangeAutoDeleteNotEquivalent() throws IOException { - channel.exchangeDeclare(NAME, "direct", false, false, null); - verifyNotEquivalent(NAME, "direct", false, true, null); - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/ExchangeDeletePredeclared.java b/test/src/com/rabbitmq/client/test/functional/ExchangeDeletePredeclared.java deleted file mode 100644 index 4f73987b71..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/ExchangeDeletePredeclared.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; - -public class ExchangeDeletePredeclared extends BrokerTestCase { - public void testDeletingPredeclaredAmqExchange() throws IOException { - try { - channel.exchangeDelete("amq.fanout"); - } catch (IOException e) { - checkShutdownSignal(AMQP.ACCESS_REFUSED, e); - } - } - - public void testDeletingPredeclaredAmqRabbitMQExchange() throws IOException { - try { - channel.exchangeDelete("amq.rabbitmq.log"); - } catch (IOException e) { - checkShutdownSignal(AMQP.ACCESS_REFUSED, e); - } - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/FrameMax.java b/test/src/com/rabbitmq/client/test/functional/FrameMax.java deleted file mode 100644 index b2e13d840f..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/FrameMax.java +++ /dev/null @@ -1,170 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.impl.ConnectionParams; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; -import java.net.Socket; -import java.util.concurrent.ExecutorService; - -import com.rabbitmq.client.Address; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.impl.AMQConnection; -import com.rabbitmq.client.impl.AMQCommand; -import com.rabbitmq.client.impl.Frame; -import com.rabbitmq.client.impl.FrameHandler; -import com.rabbitmq.client.impl.LongStringHelper; -import com.rabbitmq.client.impl.SocketFrameHandler; - -public class FrameMax extends BrokerTestCase { - /* This value for FrameMax is larger than the minimum and less - * than what Rabbit suggests. */ - final static int FRAME_MAX = 70000; - final static int REAL_FRAME_MAX = FRAME_MAX - 8; - - public FrameMax() { - connectionFactory = new MyConnectionFactory(); - connectionFactory.setRequestedFrameMax(FRAME_MAX); - } - - /* Publish a message of size FRAME_MAX. The broker should split - * this into two frames before sending back. Frame content should - * be less or equal to frame-max - 8. */ - public void testFrameSizes() - throws IOException, InterruptedException - { - String queueName = channel.queueDeclare().getQueue(); - /* This should result in at least 3 frames. */ - int howMuch = 2*FRAME_MAX; - basicPublishVolatile(new byte[howMuch], queueName); - /* Receive everything that was sent out. */ - while (howMuch > 0) { - try { - GetResponse response = channel.basicGet(queueName, false); - howMuch -= response.getBody().length; - } catch (Exception e) { - e.printStackTrace(); - fail("Exception in basicGet loop: " + e); - } - } - } - - /* server should reject frames larger than AMQP.FRAME_MIN_SIZE - * during connection negotiation */ - public void testRejectLargeFramesDuringConnectionNegotiation() - throws IOException - { - ConnectionFactory cf = new ConnectionFactory(); - cf.getClientProperties().put("too_long", LongStringHelper.asLongString(new byte[AMQP.FRAME_MIN_SIZE])); - try { - cf.newConnection(); - fail("Expected exception during connection negotiation"); - } catch (IOException e) { - } - } - - /* server should reject frames larger than the negotiated frame - * size */ - public void testRejectExceedingFrameMax() - throws IOException - { - closeChannel(); - closeConnection(); - ConnectionFactory cf = new GenerousConnectionFactory(); - connection = cf.newConnection(); - openChannel(); - basicPublishVolatile(new byte[connection.getFrameMax()], "void"); - expectError(AMQP.FRAME_ERROR); - } - - /* ConnectionFactory that uses MyFrameHandler rather than - * SocketFrameHandler. */ - private static class MyConnectionFactory extends ConnectionFactory { - protected FrameHandler createFrameHandler(Socket sock) - throws IOException - { - return new MyFrameHandler(sock); - } - } - - /* FrameHandler with added frame-max error checking. */ - private static class MyFrameHandler extends SocketFrameHandler { - public MyFrameHandler(Socket socket) - throws IOException - { - super(socket); - } - - public Frame readFrame() throws IOException { - Frame f = super.readFrame(); - int size = f.getPayload().length; - if (size > REAL_FRAME_MAX) - fail("Received frame of size " + size - + ", which exceeds " + REAL_FRAME_MAX + "."); - //System.out.printf("Received a frame of size %d.\n", f.getPayload().length); - return f; - } - } - - /* - AMQConnection with a frame_max that is one higher than what it - tells the server. - */ - private static class GenerousAMQConnection extends AMQConnection { - - public GenerousAMQConnection(ConnectionFactory factory, - FrameHandler handler, - ExecutorService executor) { - super(factory.params(executor), handler); - } - - @Override public int getFrameMax() { - // the RabbitMQ broker permits frames that are oversize by - // up to EMPTY_FRAME_SIZE octets - return super.getFrameMax() + AMQCommand.EMPTY_FRAME_SIZE + 1; - } - - } - - private static class GenerousConnectionFactory extends ConnectionFactory { - - @Override public Connection newConnection(ExecutorService executor, Address[] addrs) - throws IOException - { - IOException lastException = null; - for (Address addr : addrs) { - try { - FrameHandler frameHandler = createFrameHandlerFactory().create(addr); - AMQConnection conn = new GenerousAMQConnection(this, frameHandler, executor); - conn.start(); - return conn; - } catch (IOException e) { - lastException = e; - } - } - throw (lastException != null) ? lastException - : new IOException("failed to connect"); - } - } - -} diff --git a/test/src/com/rabbitmq/client/test/functional/FunctionalTests.java b/test/src/com/rabbitmq/client/test/functional/FunctionalTests.java deleted file mode 100644 index 86d4d49752..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/FunctionalTests.java +++ /dev/null @@ -1,83 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.impl.WorkPoolTests; -import com.rabbitmq.client.test.Bug20004Test; - -import junit.framework.TestCase; -import junit.framework.TestSuite; - -public class FunctionalTests extends TestCase { - public static TestSuite suite() { - TestSuite suite = new TestSuite("functional"); - add(suite); - return suite; - } - - public static void add(TestSuite suite) { - suite.addTestSuite(ConnectionOpen.class); - suite.addTestSuite(Heartbeat.class); - suite.addTestSuite(Tables.class); - suite.addTestSuite(DoubleDeletion.class); - suite.addTestSuite(Routing.class); - suite.addTestSuite(BindingLifecycle.class); - suite.addTestSuite(Recover.class); - suite.addTestSuite(Reject.class); - suite.addTestSuite(Transactions.class); - suite.addTestSuite(RequeueOnConnectionClose.class); - suite.addTestSuite(RequeueOnChannelClose.class); - suite.addTestSuite(DurableOnTransient.class); - suite.addTestSuite(NoRequeueOnCancel.class); - suite.addTestSuite(Bug20004Test.class); - suite.addTestSuite(ExchangeDeleteIfUnused.class); - suite.addTestSuite(QosTests.class); - suite.addTestSuite(AlternateExchange.class); - suite.addTestSuite(ExchangeExchangeBindings.class); - suite.addTestSuite(ExchangeExchangeBindingsAutoDelete.class); - suite.addTestSuite(ExchangeDeclare.class); - suite.addTestSuite(FrameMax.class); - suite.addTestSuite(QueueLifecycle.class); - suite.addTestSuite(QueueLease.class); - suite.addTestSuite(QueueExclusivity.class); - suite.addTestSuite(QueueSizeLimit.class); - suite.addTestSuite(InvalidAcks.class); - suite.addTestSuite(InvalidAcksTx.class); - suite.addTestSuite(DefaultExchange.class); - suite.addTestSuite(UnbindAutoDeleteExchange.class); - suite.addTestSuite(Confirm.class); - suite.addTestSuite(ConsumerCancelNotification.class); - suite.addTestSuite(UnexpectedFrames.class); - suite.addTestSuite(PerQueueTTL.class); - suite.addTestSuite(PerMessageTTL.class); - suite.addTestSuite(PerQueueVsPerMessageTTL.class); - suite.addTestSuite(DeadLetterExchange.class); - suite.addTestSuite(SaslMechanisms.class); - suite.addTestSuite(UserIDHeader.class); - suite.addTestSuite(InternalExchange.class); - suite.addTestSuite(CcRoutes.class); - suite.addTestSuite(WorkPoolTests.class); - suite.addTestSuite(HeadersExchangeValidation.class); - suite.addTestSuite(ConsumerPriorities.class); - suite.addTestSuite(Policies.class); - suite.addTestSuite(ConnectionRecovery.class); - suite.addTestSuite(ExceptionHandling.class); - suite.addTestSuite(PerConsumerPrefetch.class); - suite.addTestSuite(DirectReplyTo.class); - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/Heartbeat.java b/test/src/com/rabbitmq/client/test/functional/Heartbeat.java deleted file mode 100644 index 7de1c85ec2..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/Heartbeat.java +++ /dev/null @@ -1,46 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.functional; - -import java.io.IOException; - -import com.rabbitmq.client.impl.AMQConnection; -import com.rabbitmq.client.test.BrokerTestCase; - -public class Heartbeat extends BrokerTestCase { - - public Heartbeat() - { - super(); - connectionFactory.setRequestedHeartbeat(1); - } - - public void testHeartbeat() - throws IOException, InterruptedException - { - assertEquals(1, connection.getHeartbeat()); - Thread.sleep(3100); - assertTrue(connection.isOpen()); - ((AMQConnection)connection).setHeartbeat(0); - assertEquals(0, connection.getHeartbeat()); - Thread.sleep(3100); - assertFalse(connection.isOpen()); - - } - -} diff --git a/test/src/com/rabbitmq/client/test/functional/InvalidAcks.java b/test/src/com/rabbitmq/client/test/functional/InvalidAcks.java deleted file mode 100644 index 117451877d..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/InvalidAcks.java +++ /dev/null @@ -1,25 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test.functional; - -import java.io.IOException; - -public class InvalidAcks extends InvalidAcksBase { - protected void select() throws IOException {} - protected void commit() throws IOException {} -} - diff --git a/test/src/com/rabbitmq/client/test/functional/InvalidAcksTx.java b/test/src/com/rabbitmq/client/test/functional/InvalidAcksTx.java deleted file mode 100644 index e6ef6524ae..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/InvalidAcksTx.java +++ /dev/null @@ -1,29 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test.functional; - -import java.io.IOException; - -public class InvalidAcksTx extends InvalidAcksBase { - protected void select() throws IOException { - channel.txSelect(); - } - - protected void commit() throws IOException { - channel.txCommit(); - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/NoRequeueOnCancel.java b/test/src/com/rabbitmq/client/test/functional/NoRequeueOnCancel.java deleted file mode 100644 index cda8ade377..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/NoRequeueOnCancel.java +++ /dev/null @@ -1,56 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.test.BrokerTestCase; -import java.io.IOException; - -import com.rabbitmq.client.QueueingConsumer; - -public class NoRequeueOnCancel extends BrokerTestCase -{ - protected final String Q = "NoRequeueOnCancel"; - - protected void createResources() throws IOException { - channel.queueDeclare(Q, false, false, false, null); - } - - protected void releaseResources() throws IOException { - channel.queueDelete(Q); - } - - public void testNoRequeueOnCancel() - throws IOException, InterruptedException - { - channel.basicPublish("", Q, null, "1".getBytes()); - - QueueingConsumer c; - - c = new QueueingConsumer(channel); - String consumerTag = channel.basicConsume(Q, false, c); - c.nextDelivery(); - channel.basicCancel(consumerTag); - - assertNull(channel.basicGet(Q, true)); - - closeChannel(); - openChannel(); - - assertNotNull(channel.basicGet(Q, true)); - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/Reject.java b/test/src/com/rabbitmq/client/test/functional/Reject.java deleted file mode 100644 index 6108841b0a..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/Reject.java +++ /dev/null @@ -1,51 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.QueueingConsumer; - -import java.io.IOException; - -public class Reject extends AbstractRejectTest -{ - public void testReject() - throws IOException, InterruptedException - { - String q = channel.queueDeclare("", false, true, false, null).getQueue(); - - byte[] m1 = "1".getBytes(); - byte[] m2 = "2".getBytes(); - - basicPublishVolatile(m1, q); - basicPublishVolatile(m2, q); - - long tag1 = checkDelivery(channel.basicGet(q, false), m1, false); - long tag2 = checkDelivery(channel.basicGet(q, false), m2, false); - QueueingConsumer c = new QueueingConsumer(secondaryChannel); - String consumerTag = secondaryChannel.basicConsume(q, false, c); - channel.basicReject(tag2, true); - long tag3 = checkDelivery(c.nextDelivery(), m2, true); - secondaryChannel.basicCancel(consumerTag); - secondaryChannel.basicReject(tag3, false); - assertNull(channel.basicGet(q, false)); - channel.basicAck(tag1, false); - channel.basicReject(tag3, false); - expectError(AMQP.PRECONDITION_FAILED); - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/RequeueOnChannelClose.java b/test/src/com/rabbitmq/client/test/functional/RequeueOnChannelClose.java deleted file mode 100644 index 7554d5b8a0..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/RequeueOnChannelClose.java +++ /dev/null @@ -1,35 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.functional; - -import java.io.IOException; - -public class RequeueOnChannelClose extends RequeueOnClose -{ - - protected void open() throws IOException - { - openChannel(); - } - - protected void close() throws IOException - { - closeChannel(); - } - -} diff --git a/test/src/com/rabbitmq/client/test/functional/RequeueOnConnectionClose.java b/test/src/com/rabbitmq/client/test/functional/RequeueOnConnectionClose.java deleted file mode 100644 index 5d61fb0f2d..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/RequeueOnConnectionClose.java +++ /dev/null @@ -1,36 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.functional; - -import java.io.IOException; - -public class RequeueOnConnectionClose extends RequeueOnClose -{ - - protected void open() throws IOException - { - openConnection(); - openChannel(); - } - - protected void close() throws IOException - { - closeConnection(); - } - -} diff --git a/test/src/com/rabbitmq/client/test/functional/UnbindAutoDeleteExchange.java b/test/src/com/rabbitmq/client/test/functional/UnbindAutoDeleteExchange.java deleted file mode 100644 index 620fd01a25..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/UnbindAutoDeleteExchange.java +++ /dev/null @@ -1,44 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; - -/** - * Test that unbinding from an auto-delete exchange causes the exchange to go - * away - */ -public class UnbindAutoDeleteExchange extends BrokerTestCase { - public void testUnbind() throws IOException, InterruptedException { - String exchange = "myexchange"; - channel.exchangeDeclare(exchange, "fanout", false, true, null); - String queue = channel.queueDeclare().getQueue(); - channel.queueBind(queue, exchange, ""); - channel.queueUnbind(queue, exchange, ""); - - try { - channel.exchangeDeclarePassive(exchange); - fail("exchange should no longer be there"); - } - catch (IOException e) { - checkShutdownSignal(AMQP.NOT_FOUND, e); - } - } -} diff --git a/test/src/com/rabbitmq/client/test/functional/UserIDHeader.java b/test/src/com/rabbitmq/client/test/functional/UserIDHeader.java deleted file mode 100644 index 3dc05c0463..0000000000 --- a/test/src/com/rabbitmq/client/test/functional/UserIDHeader.java +++ /dev/null @@ -1,61 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test.functional; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.AlreadyClosedException; -import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.tools.Host; - -import java.io.IOException; - -public class UserIDHeader extends BrokerTestCase { - private static final AMQP.BasicProperties GOOD = new AMQP.BasicProperties.Builder().userId("guest").build(); - private static final AMQP.BasicProperties BAD = new AMQP.BasicProperties.Builder().userId("not the guest, honest").build(); - - public void testValidUserId() throws IOException { - publish(GOOD); - } - - public void testInvalidUserId() { - try { - publish(BAD); - fail("Accepted publish with incorrect user ID"); - } catch (IOException e) { - checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); - } catch (AlreadyClosedException e) { - checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); - } - } - - public void testImpersonatedUserId() throws IOException { - Host.rabbitmqctl("set_user_tags guest administrator impersonator"); - connection = null; - channel = null; - setUp(); - try { - publish(BAD); - } finally { - Host.rabbitmqctl("set_user_tags guest administrator"); - } - } - - private void publish(AMQP.BasicProperties properties) throws IOException { - channel.basicPublish("amq.fanout", "", properties, "".getBytes()); - channel.queueDeclare(); // To flush the channel - } -} diff --git a/test/src/com/rabbitmq/client/test/performance/CLIHelper.java b/test/src/com/rabbitmq/client/test/performance/CLIHelper.java deleted file mode 100644 index 39e1f9de68..0000000000 --- a/test/src/com/rabbitmq/client/test/performance/CLIHelper.java +++ /dev/null @@ -1,82 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.performance; - -import java.util.Iterator; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.GnuParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; - -/** - * Super class for handling repetative CLI stuff - */ -public class CLIHelper { - - private final Options options = new Options(); - - public static CLIHelper defaultHelper() { - Options opts = new Options(); - opts.addOption(new Option( "help", "print this message")); - opts.addOption(new Option("h", "host", true, "broker host")); - opts.addOption(new Option("p", "port", true, "broker port")); - return new CLIHelper(opts); - } - - public CLIHelper(Options opts) { - Iterator it = opts.getOptions().iterator(); - while (it.hasNext()) { - options.addOption((Option) it.next()); - } - } - - public void addOption(Option option) { - options.addOption(option); - } - - public CommandLine parseCommandLine(String [] args) { - CommandLineParser parser = new GnuParser(); - CommandLine commandLine = null; - try { - commandLine = parser.parse(options, args); - } - catch (ParseException e) { - printHelp(options); - throw new RuntimeException("Parsing failed. Reason: " + e.getMessage()); - } - - if (commandLine.hasOption("help")) { - printHelp(options); - return null; - } - return commandLine; - } - - public void printHelp(Options options) { - HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp(getClass().getSimpleName(), options); - } - - public static int getOptionValue(CommandLine cmd, String s, int i) { - return Integer.parseInt(cmd.getOptionValue(s, i + "")); - } -} diff --git a/test/src/com/rabbitmq/client/test/performance/QosScaling.java b/test/src/com/rabbitmq/client/test/performance/QosScaling.java deleted file mode 100644 index 9f959176ba..0000000000 --- a/test/src/com/rabbitmq/client/test/performance/QosScaling.java +++ /dev/null @@ -1,149 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.performance; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.QueueingConsumer; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.Option; - -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; - -public class QosScaling { - - protected static class Parameters { - final String host; - final int port; - final int messageCount; - final int queueCount; - final int emptyCount; - - public static CommandLine parseCommandLine(String[] args) { - CLIHelper helper = CLIHelper.defaultHelper(); - helper.addOption(new Option("n", "messages", true, "number of messages to send")); - helper.addOption(new Option("q", "queues", true, "number of queues to route messages to")); - helper.addOption(new Option("e", "empty", true, "number of queues to leave empty")); - return helper.parseCommandLine(args); - } - - public Parameters(CommandLine cmd) { - host = cmd.getOptionValue("h", "localhost"); - port = CLIHelper.getOptionValue(cmd, "p", AMQP.PROTOCOL.PORT); - messageCount = CLIHelper.getOptionValue(cmd, "n", 2000); - queueCount = CLIHelper.getOptionValue(cmd, "q", 100); - emptyCount = CLIHelper.getOptionValue(cmd, "e", 0); - } - - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("host=" + host); - b.append(",port=" + port); - b.append(",messages=" + messageCount); - b.append(",queues=" + queueCount); - b.append(",empty=" + emptyCount); - return b.toString(); - } - - } - - protected final Parameters params; - protected final ConnectionFactory connectionFactory = - new ConnectionFactory(); - protected Connection connection; - protected Channel channel; - - public QosScaling(Parameters p) { - params = p; - } - - protected List consume(QueueingConsumer c) throws IOException { - for (int i = 0; i < params.emptyCount; i++) { - String queue = channel.queueDeclare().getQueue(); - channel.basicConsume(queue, false, c); - } - List queues = new ArrayList(); - for (int i = 0; i < params.queueCount; i++) { - String queue = channel.queueDeclare().getQueue(); - channel.basicConsume(queue, false, c); - queues.add(queue); - } - return queues; - } - - protected void publish(List queues) throws IOException { - byte[] body = "".getBytes(); - int messagesPerQueue = params.messageCount / queues.size(); - for (String queue : queues) { - for (int i = 0; i < messagesPerQueue; i++) { - channel.basicPublish("", queue, null, body); - } - } - //ensure that all the messages have reached the queues - for (String queue : queues) { - channel.queueDeclarePassive(queue); - } - } - - protected long drain(QueueingConsumer c) throws IOException { - long start = System.nanoTime(); - try { - for (int i = 0; i < params.messageCount; i++) { - long tag = c.nextDelivery().getEnvelope().getDeliveryTag(); - channel.basicAck(tag, false); - } - } catch (InterruptedException e) { - IOException ioe = new IOException(); - ioe.initCause(e); - throw ioe; - } - long finish = System.nanoTime(); - return finish - start; - } - - public long run() throws IOException { - connectionFactory.setHost(params.host); - connectionFactory.setPort(params.port); - connection = connectionFactory.newConnection(); - channel = connection.createChannel(); - channel.basicQos(1); - QueueingConsumer consumer = new QueueingConsumer(channel); - try { - publish(consume(consumer)); - return drain(consumer); - } finally { - connection.abort(); - } - } - - public static void main(String[] args) throws Exception { - CommandLine cmd = Parameters.parseCommandLine(args); - if (cmd == null) return; - Parameters params = new Parameters(cmd); - System.out.print(params.toString()); - QosScaling test = new QosScaling(params); - long result = test.run(); - System.out.println(" -> " + result / 1000000 + "ms"); - } - -} diff --git a/test/src/com/rabbitmq/client/test/performance/ScalabilityTest.java b/test/src/com/rabbitmq/client/test/performance/ScalabilityTest.java deleted file mode 100644 index 96003f88eb..0000000000 --- a/test/src/com/rabbitmq/client/test/performance/ScalabilityTest.java +++ /dev/null @@ -1,367 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.performance; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.util.Random; -import java.util.Stack; -import java.util.UUID; -import java.util.Vector; -import java.util.concurrent.CountDownLatch; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.Option; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.ReturnListener; - -/** - * This tests the scalability of the routing tables in two aspects: - * - * 1. The rate of creation and deletion for a fixed level of bindings - * per queue accross varying amounts of queues; - * - * 2. The rate of publishing n messages to an exchange with a fixed - * amount of bindings per queue accross varying amounts of queues. - */ -public class ScalabilityTest { - - private static class Parameters { - String host; - int port; - int messageCount; - int base, maxQueueExp, maxBindingExp, maxExp; - String filePrefix; - - } - - private abstract static class Measurements { - - protected final long[] times; - private final long start; - - public Measurements(final int count) { - times = new long[count]; - start = System.nanoTime(); - } - - public void addDataPoint(final int i) { - times[i] = System.nanoTime() - start; - } - - abstract public float[] analyse(final int base); - - protected static float[] calcOpTimes(final int base, final long[] t) { - float[] r = new float[t.length]; - for (int i = 0; i < t.length; i ++) { - final int amount = pow(base, i); - r[i] = t[i] / (float) amount / 1000; - } - - return r; - } - - } - - private static class CreationMeasurements extends Measurements { - - public CreationMeasurements(final int count) { - super(count); - } - - public float[] analyse(final int base) { - return calcOpTimes(base, times); - } - - } - - private static class DeletionMeasurements extends Measurements { - - public DeletionMeasurements(final int count) { - super(count); - } - - public float[] analyse(final int base) { - final long tmp[] = new long[times.length]; - final long totalTime = times[0]; - int i; - for (i = 0; i < times.length - 1; i++) { - tmp[i] = totalTime - times[i + 1]; - } - tmp[i] = totalTime; - - return calcOpTimes(base, tmp); - } - - } - - private static class Results { - - final float[][] creationTimes; - final float[][] deletionTimes; - final float[][] routingTimes; - - public Results(final int y) { - creationTimes = new float[y][]; - deletionTimes = new float[y][]; - routingTimes = new float[y][]; - } - - public void print(final int base, final String prefix) - throws IOException { - - PrintStream s; - s = open(prefix, "creation"); - print(s, base, creationTimes); - s.close(); - s = open(prefix, "deletion"); - print(s, base, deletionTimes); - s.close(); - s = open(prefix, "routing"); - print(s, base, transpose(routingTimes)); - s.close(); - } - - private static PrintStream open(final String prefix, - final String suffix) - throws IOException { - - return new PrintStream(new FileOutputStream(prefix + suffix + - ".dat")); - } - - private static void print(final PrintStream s, final int base, - final float[][] times) { - for (int y = 0; y < times.length; y++) { - s.println("# level " + pow(base, y)); - for (int x = 0; x < times[y].length; x++) { - s.println(pow(base, x) + " " + format.format(times[y][x])); - } - s.println(); - s.println(); - } - } - - private float[][] transpose(float[][] m) { - Vector> tmp = new Vector>(); - for (int i = 0; i < m[0].length; i++) { - tmp.addElement(new Vector()); - } - for (int i = 0; i < m.length; i++) { - for (int j = 0; j < m[i].length; j++) { - Vector v = tmp.get(j); - v.addElement(m[i][j]); - } - } - float[][] r = new float[tmp.size()][]; - for (int i = 0; i < tmp.size(); i++) { - Vector v = tmp.get(i); - float[] vr = new float[v.size()]; - for (int j = 0; j < v.size(); j++) { - vr[j] = v.get(j); - } - r[i] = vr; - } - return r; - } - } - - private static final NumberFormat format = new DecimalFormat("0.00"); - - private final Parameters params; - - public ScalabilityTest(Parameters p) { - params = p; - } - - public static void main(String[] args) throws Exception { - Parameters params = parseArgs(args); - if (params == null) return; - - ScalabilityTest test = new ScalabilityTest(params); - Results r = test.run(); - if (params.filePrefix != null) - r.print(params.base, params.filePrefix); - } - - - public Results run() throws Exception{ - Connection con = new ConnectionFactory(){{setHost(params.host); setPort(params.port);}}.newConnection(); - Channel channel = con.createChannel(); - - Results r = new Results(params.maxBindingExp); - - for (int y = 0; y < params.maxBindingExp; y++) { - - final int maxBindings = pow(params.base, y); - - String[] routingKeys = new String[maxBindings]; - for (int b = 0; b < maxBindings; b++) { - routingKeys[b] = UUID.randomUUID().toString(); - } - - Stack queues = new Stack(); - - int maxQueueExp = Math.min(params.maxQueueExp, params.maxExp - y); - - System.out.println("---------------------------------"); - System.out.println("| bindings = " + maxBindings + ", messages = " + params.messageCount); - - System.out.println("| Routing"); - - int q = 0; - - // create queues & bindings, time routing - Measurements creation = new CreationMeasurements(maxQueueExp); - float routingTimes[] = new float[maxQueueExp]; - for (int x = 0; x < maxQueueExp; x++) { - - final int maxQueues = pow(params.base, x); - - for (; q < maxQueues; q++) { - AMQP.Queue.DeclareOk ok = channel.queueDeclare(); - queues.push(ok.getQueue()); - for (int b = 0; b < maxBindings; b++) { - channel.queueBind(ok.getQueue(), "amq.direct", routingKeys[b]); - } - } - - creation.addDataPoint(x); - - float routingTime = timeRouting(channel, routingKeys); - routingTimes[x] = routingTime; - printTime(params.base, x, routingTime); - } - - r.routingTimes[y] = routingTimes; - float[] creationTimes = creation.analyse(params.base); - r.creationTimes[y] = creationTimes; - System.out.println("| Creating"); - printTimes(params.base, creationTimes); - - // delete queues & bindings - Measurements deletion = new DeletionMeasurements(maxQueueExp); - for (int x = maxQueueExp - 1; x >= 0; x--) { - - final int maxQueues = (x == 0) ? 0 : pow(params.base, x - 1); - - for (; q > maxQueues; q--) { - channel.queueDelete(queues.pop()); - } - - deletion.addDataPoint(x); - } - - float[] deletionTimes = deletion.analyse(params.base); - r.deletionTimes[y] = deletionTimes; - System.out.println("| Deleting"); - printTimes(params.base, deletionTimes); - } - - channel.close(); - con.close(); - - return r; - } - - private float timeRouting(Channel channel, String[] routingKeys) - throws IOException, InterruptedException { - - boolean mandatory = true; - boolean immdediate = true; - final CountDownLatch latch = new CountDownLatch(params.messageCount); - channel.addReturnListener(new ReturnListener() { - public void handleReturn(int replyCode, String replyText, - String exchange, String routingKey, - AMQP.BasicProperties properties, byte[] body) throws IOException { - latch.countDown(); - } - }); - - final long start = System.nanoTime(); - - // route some messages - Random r = new Random(); - int size = routingKeys.length; - for (int n = 0; n < params.messageCount; n ++) { - String key = routingKeys[r.nextInt(size)]; - channel.basicPublish("amq.direct", key, true, false, - MessageProperties.MINIMAL_BASIC, null); - } - - // wait for the returns to come back - latch.await(); - - // Compute the roundtrip time - final long finish = System.nanoTime(); - final long wallclock = finish - start; - return (params.messageCount == 0) ? (float)0.0 : wallclock / (float) params.messageCount / 1000; - } - - private static Parameters parseArgs(String [] args) { - CLIHelper helper = CLIHelper.defaultHelper(); - - helper.addOption(new Option("n", "messages", true, "number of messages to send")); - helper.addOption(new Option("b", "base", true, "base for exponential scaling")); - helper.addOption(new Option("x", "q-max-exp", true, "maximum queue count exponent")); - helper.addOption(new Option("y", "b-max-exp", true, "maximum per-queue binding count exponent")); - helper.addOption(new Option("c", "c-max-exp", true, "combined maximum exponent")); - helper.addOption(new Option("f", "file", true, "result files prefix; defaults to no file output")); - - CommandLine cmd = helper.parseCommandLine(args); - if (null == cmd) return null; - - Parameters params = new Parameters(); - params.host = cmd.getOptionValue("h", "0.0.0.0"); - params.port = CLIHelper.getOptionValue(cmd, "p", AMQP.PROTOCOL.PORT); - params.messageCount = CLIHelper.getOptionValue(cmd, "n", 100); - params.base = CLIHelper.getOptionValue(cmd, "b", 10); - params.maxQueueExp = CLIHelper.getOptionValue(cmd, "x", 4); - params.maxBindingExp = CLIHelper.getOptionValue(cmd, "y", 4); - params.maxExp = CLIHelper.getOptionValue(cmd, "c", Math.max(params.maxQueueExp, params.maxBindingExp)); - params.filePrefix = cmd.getOptionValue("f", null); - - return params; - } - - private static int pow(int x, int y) { - int r = 1; - for( int i = 0; i < y; i++ ) r *= x; - return r; - } - - private static void printTimes(int base, float[] times) { - for (int i = 0; i < times.length; i ++) { - printTime(base, i, times[i]); - } - } - - private static void printTime(int base, int exp, float v) { - System.out.println("| " + pow(base, exp) + - " -> " + format.format(v) + " us/op"); - } - -} diff --git a/test/src/com/rabbitmq/client/test/performance/StressManagement.java b/test/src/com/rabbitmq/client/test/performance/StressManagement.java deleted file mode 100644 index a0656b073d..0000000000 --- a/test/src/com/rabbitmq/client/test/performance/StressManagement.java +++ /dev/null @@ -1,116 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test.performance; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.MessageProperties; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.Option; - -import java.io.IOException; - -public class StressManagement { - protected static class Parameters { - final String host; - final int port; - final int queueCount; - final int channelCount; - - public static CommandLine parseCommandLine(String[] args) { - CLIHelper helper = CLIHelper.defaultHelper(); - helper.addOption(new Option("q", "queues", true, "number of queues")); - helper.addOption(new Option("c", "channels", true, "number of channels")); - return helper.parseCommandLine(args); - } - - public Parameters(CommandLine cmd) { - host = cmd.getOptionValue("h", "localhost"); - port = CLIHelper.getOptionValue(cmd, "p", AMQP.PROTOCOL.PORT); - queueCount = CLIHelper.getOptionValue(cmd, "q", 5000); - channelCount = CLIHelper.getOptionValue(cmd, "c", 100); - } - - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("host=" + host); - b.append(",port=" + port); - b.append(",queues=" + queueCount); - b.append(",channels=" + channelCount); - return b.toString(); - } - - } - - protected final Parameters params; - protected final ConnectionFactory connectionFactory = - new ConnectionFactory(); - protected Connection connection; - protected Channel publishChannel; - protected Channel[] channels; - - public StressManagement(Parameters p) { - params = p; - } - - public long run() throws IOException { - connectionFactory.setHost(params.host); - connectionFactory.setPort(params.port); - connection = connectionFactory.newConnection(); - publishChannel = connection.createChannel(); - - System.out.println("Declaring..."); - - channels = new Channel[params.channelCount]; - for (int i = 0; i < params.channelCount; i++) { - channels[i] = connection.createChannel(); - } - - for (int i = 0; i < params.queueCount; i++) { - publishChannel.queueDeclare("queue-" + i, false, true, false, null); - publishChannel.queueBind("queue-" + i, "amq.fanout", ""); - } - - System.out.println("Declaration complete, running..."); - - while (true) { - for (int i = 0; i < params.channelCount; i++) { - publishChannel.basicPublish("amq.fanout", "", MessageProperties.BASIC, "".getBytes()); - for (int j = 0; j < params.queueCount; j++) { - while (channels[i].basicGet("queue-" + j, true) == null) { - try { - Thread.sleep(10); - } catch (InterruptedException e) { - - } - } - } - } - } - } - - public static void main(String[] args) throws Exception { - CommandLine cmd = Parameters.parseCommandLine(args); - if (cmd == null) return; - Parameters params = new Parameters(cmd); - System.out.println(params.toString()); - StressManagement test = new StressManagement(params); - test.run(); - } -} diff --git a/test/src/com/rabbitmq/client/test/server/AbsentQueue.java b/test/src/com/rabbitmq/client/test/server/AbsentQueue.java deleted file mode 100644 index 7941db0483..0000000000 --- a/test/src/com/rabbitmq/client/test/server/AbsentQueue.java +++ /dev/null @@ -1,99 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.server; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.test.functional.ClusteredTestBase; -import com.rabbitmq.tools.Host; - -import java.io.IOException; - -/** - * This tests whether 'absent' queues - durable queues whose home node - * is down - are handled properly. - */ -public class AbsentQueue extends ClusteredTestBase { - - private static final String Q = "absent-queue"; - - @Override protected void setUp() throws IOException { - super.setUp(); - if (clusteredConnection != null) - stopSecondary(); - } - - @Override protected void tearDown() throws IOException { - if (clusteredConnection != null) - startSecondary(); - super.tearDown(); - } - - @Override protected void createResources() throws IOException { - alternateChannel.queueDeclare(Q, true, false, false, null); - } - - @Override protected void releaseResources() throws IOException { - alternateChannel.queueDelete(Q); - } - - public void testNotFound() throws IOException { - assertNotFound(new Task() { - public void run() throws IOException { - channel.queueDeclare(Q, true, false, false, null); - } - }); - assertNotFound(new Task() { - public void run() throws IOException { - channel.queueDeclarePassive(Q); - } - }); - assertNotFound(new Task() { - public void run() throws IOException { - channel.queuePurge(Q); - } - }); - assertNotFound(new Task() { - public void run() throws IOException { - channel.basicGet(Q, true); - } - }); - assertNotFound(new Task() { - public void run() throws IOException { - channel.queueBind(Q, "amq.fanout", "", null); - } - }); - } - - protected void assertNotFound(Task t) throws IOException { - if (clusteredChannel == null) return; - try { - t.run(); - if (!HATests.HA_TESTS_RUNNING) fail("expected not_found"); - } catch (IOException ioe) { - assertFalse(HATests.HA_TESTS_RUNNING); - checkShutdownSignal(AMQP.NOT_FOUND, ioe); - channel = connection.createChannel(); - } - - } - - private interface Task { - public void run() throws IOException; - } - -} diff --git a/test/src/com/rabbitmq/client/test/server/AlternateExchangeEquivalence.java b/test/src/com/rabbitmq/client/test/server/AlternateExchangeEquivalence.java deleted file mode 100644 index 683e067ebc..0000000000 --- a/test/src/com/rabbitmq/client/test/server/AlternateExchangeEquivalence.java +++ /dev/null @@ -1,43 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.server; - -import java.util.Map; -import java.util.HashMap; -import java.io.IOException; - -import com.rabbitmq.client.test.functional.ExchangeEquivalenceBase; - -public class AlternateExchangeEquivalence extends ExchangeEquivalenceBase { - static final Map args = new HashMap(); - { - args.put("alternate-exchange", "UME"); - } - - public void testAlternateExchangeEquivalence() throws IOException { - channel.exchangeDeclare("alternate", "direct", false, false, args); - verifyEquivalent("alternate", "direct", false, false, args); - } - - public void testAlternateExchangeNonEquivalence() throws IOException { - channel.exchangeDeclare("alternate", "direct", false, false, args); - Map altargs = new HashMap(); - altargs.put("alternate-exchange", "somewhere"); - verifyNotEquivalent("alternate", "direct", false, false, altargs); - } -} diff --git a/test/src/com/rabbitmq/client/test/server/EffectVisibilityCrossNodeTest.java b/test/src/com/rabbitmq/client/test/server/EffectVisibilityCrossNodeTest.java deleted file mode 100644 index 630d06af84..0000000000 --- a/test/src/com/rabbitmq/client/test/server/EffectVisibilityCrossNodeTest.java +++ /dev/null @@ -1,62 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test.server; - -import com.rabbitmq.client.test.functional.ClusteredTestBase; - -import java.io.IOException; - -/** - * From bug 19844 - we want to be sure that publish vs everything else can't - * happen out of order - */ -public class EffectVisibilityCrossNodeTest extends ClusteredTestBase { - private final String[] queues = new String[QUEUES]; - - @Override - protected void createResources() throws IOException { - for (int i = 0; i < queues.length ; i++) { - queues[i] = alternateChannel.queueDeclare("", false, false, true, null).getQueue(); - alternateChannel.queueBind(queues[i], "amq.fanout", ""); - } - } - - @Override - protected void releaseResources() throws IOException { - for (int i = 0; i < queues.length ; i++) { - alternateChannel.queueDelete(queues[i]); - } - } - - private static final int QUEUES = 5; - private static final int BATCHES = 500; - private static final int MESSAGES_PER_BATCH = 10; - - private static final byte[] msg = "".getBytes(); - - public void testEffectVisibility() throws Exception { - - for (int i = 0; i < BATCHES; i++) { - for (int j = 0; j < MESSAGES_PER_BATCH; j++) { - channel.basicPublish("amq.fanout", "", null, msg); - } - for (int j = 0; j < queues.length ; j++) { - assertEquals(MESSAGES_PER_BATCH, channel.queuePurge(queues[j]).getMessageCount()); - } - } - } -} diff --git a/test/src/com/rabbitmq/client/test/server/ExclusiveQueueDurability.java b/test/src/com/rabbitmq/client/test/server/ExclusiveQueueDurability.java deleted file mode 100644 index 4eb20b338a..0000000000 --- a/test/src/com/rabbitmq/client/test/server/ExclusiveQueueDurability.java +++ /dev/null @@ -1,72 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.server; - -import java.io.IOException; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.tools.Host; - -/** - * This tests whether exclusive, durable queues are deleted when appropriate - * (following the scenarios given in bug 20578). - */ -public class ExclusiveQueueDurability extends BrokerTestCase { - - void verifyQueueMissing(Channel channel, String queueName) - throws IOException { - try { - channel.queueDeclare(queueName, false, false, false, null); - } catch (IOException ioe) { - checkShutdownSignal(AMQP.RESOURCE_LOCKED, ioe); - fail("Declaring the queue resulted in a channel exception, probably meaning that it already exists"); - } - } - - // 1) connection and queue are on same node, node restarts -> queue - // should no longer exist - public void testConnectionQueueSameNode() throws Exception { - channel.queueDeclare("scenario1", true, true, false, null); - restartPrimaryAbruptly(); - verifyQueueMissing(channel, "scenario1"); - } - - private void restartPrimaryAbruptly() throws IOException { - connection = null; - channel = null; - bareRestart(); - setUp(); - } - - /* - * The other scenarios: - * - * 2) connection and queue are on different nodes, queue's node restarts, - * connection is still alive -> queue should exist - * - * 3) connection and queue are on different nodes, queue's node restarts, - * connection has been terminated in the meantime -> queue should no longer - * exist - * - * There's no way to test these, as things stand; connections and queues are - * tied to nodes, so one can't engineer a situation in which a connection - * and its exclusive queue are on different nodes. - */ -} diff --git a/test/src/com/rabbitmq/client/test/server/HATests.java b/test/src/com/rabbitmq/client/test/server/HATests.java deleted file mode 100644 index 80d970bdad..0000000000 --- a/test/src/com/rabbitmq/client/test/server/HATests.java +++ /dev/null @@ -1,59 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test.server; - -import com.rabbitmq.client.test.functional.FunctionalTests; -import com.rabbitmq.tools.Host; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -public class HATests extends TestSuite { - // this is horrific - public static boolean HA_TESTS_RUNNING = false; - - public static TestSuite suite() { - TestSuite suite = new TestSuite("server-tests"); - suite.addTestSuite(SetUp.class); - FunctionalTests.add(suite); - ServerTests.add(suite); - suite.addTestSuite(TearDown.class); - return suite; - } - - // This is of course an abuse of the TestCase concept - but I don't want to - // run this command on every test case. And there's no hook for "before / - // after this test suite". - public static class SetUp extends TestCase { - @Override - protected void setUp() throws Exception { - Host.invokeMakeTarget("enable-ha"); - HA_TESTS_RUNNING = true; - } - - public void testNothing() {} - } - - public static class TearDown extends TestCase { - @Override - protected void tearDown() throws Exception { - Host.invokeMakeTarget("disable-ha"); - HA_TESTS_RUNNING = false; - } - - public void testNothing() {} - } -} diff --git a/test/src/com/rabbitmq/client/test/server/MessageRecovery.java b/test/src/com/rabbitmq/client/test/server/MessageRecovery.java deleted file mode 100644 index 100058370a..0000000000 --- a/test/src/com/rabbitmq/client/test/server/MessageRecovery.java +++ /dev/null @@ -1,66 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. - -package com.rabbitmq.client.test.server; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.test.ConfirmBase; - -import java.io.IOException; - -public class MessageRecovery extends ConfirmBase -{ - - private final static String Q = "recovery-test"; - private final static String Q2 = "recovery-test-ha-check"; - - public void testMessageRecovery() - throws Exception - { - channel.confirmSelect(); - channel.queueDeclare(Q, true, false, false, null); - channel.basicPublish("", Q, false, false, - MessageProperties.PERSISTENT_BASIC, - "nop".getBytes()); - waitForConfirms(); - - channel.queueDeclare(Q2, false, false, false, null); - - restart(); - - // When testing in HA mode the message will be collected from - // a promoted slave and will have its redelivered flag - // set. But that only happens if there actually *is* a - // slave. We test that by passively declaring, and - // subsequently deletign, the secondary, non-durable queue, - // which only succeeds if the queue survived the restart, - // which in turn implies that it must have been a HA queue - // with slave(s). - boolean expectDelivered = false; - try { - channel.queueDeclarePassive(Q2); - channel.queueDelete(Q2); - expectDelivered = true; - } catch (IOException e) { - checkShutdownSignal(AMQP.NOT_FOUND, e); - openChannel(); - } - assertDelivered(Q, 1, expectDelivered); - channel.queueDelete(Q); - } - -} diff --git a/test/src/com/rabbitmq/client/test/server/PersistenceGuarantees.java b/test/src/com/rabbitmq/client/test/server/PersistenceGuarantees.java deleted file mode 100644 index 096211438b..0000000000 --- a/test/src/com/rabbitmq/client/test/server/PersistenceGuarantees.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.rabbitmq.client.test.server; - -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; - -public class PersistenceGuarantees extends BrokerTestCase { - private static final int COUNT = 10000; - private String queue; - - protected void declareQueue() throws IOException { - queue = channel.queueDeclare("", true, false, false, null).getQueue(); - } - - public void testTxPersistence() throws Exception { - declareQueue(); - channel.txSelect(); - publish(); - channel.txCommit(); - restart(); - assertPersisted(); - } - - public void testConfirmPersistence() throws Exception { - declareQueue(); - channel.confirmSelect(); - publish(); - channel.waitForConfirms(); - restart(); - assertPersisted(); - } - - private void assertPersisted() throws IOException { - assertEquals(COUNT, channel.queueDelete(queue).getMessageCount()); - } - - private void publish() throws IOException { - for (int i = 0; i < COUNT; i++) { - channel.basicPublish("", queue, false, false, MessageProperties.PERSISTENT_BASIC, "".getBytes()); - } - } -} diff --git a/test/src/com/rabbitmq/client/test/server/ServerTests.java b/test/src/com/rabbitmq/client/test/server/ServerTests.java deleted file mode 100644 index 30b923bfa9..0000000000 --- a/test/src/com/rabbitmq/client/test/server/ServerTests.java +++ /dev/null @@ -1,47 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.server; - -import junit.framework.TestCase; -import junit.framework.TestSuite; - -public class ServerTests extends TestCase { - public static TestSuite suite() { - TestSuite suite = new TestSuite("server-tests"); - add(suite); - return suite; - } - - public static void add(TestSuite suite) { - suite.addTestSuite(Permissions.class); - suite.addTestSuite(DurableBindingLifecycle.class); - suite.addTestSuite(DeadLetterExchangeDurable.class); - suite.addTestSuite(EffectVisibilityCrossNodeTest.class); - suite.addTestSuite(ExclusiveQueueDurability.class); - suite.addTestSuite(AbsentQueue.class); - suite.addTestSuite(AlternateExchangeEquivalence.class); - suite.addTestSuite(MemoryAlarms.class); - suite.addTestSuite(MessageRecovery.class); - suite.addTestSuite(Firehose.class); - suite.addTestSuite(PersistenceGuarantees.class); - suite.addTestSuite(Shutdown.class); - suite.addTestSuite(BlockedConnection.class); - suite.addTestSuite(ChannelLimitNegotiation.class); - suite.addTestSuite(LoopbackUsers.class); - } -} diff --git a/test/src/com/rabbitmq/client/test/server/Shutdown.java b/test/src/com/rabbitmq/client/test/server/Shutdown.java deleted file mode 100644 index 7280eb8be8..0000000000 --- a/test/src/com/rabbitmq/client/test/server/Shutdown.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.rabbitmq.client.test.server; - -import com.rabbitmq.client.test.BrokerTestCase; -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; - -import java.io.IOException; - -public class Shutdown extends BrokerTestCase { - - public void testErrorOnShutdown() throws Exception { - bareRestart(); - expectError(AMQP.CONNECTION_FORCED); - } - -} diff --git a/test/src/com/rabbitmq/client/test/server/XDeathHeaderGrowth.java b/test/src/com/rabbitmq/client/test/server/XDeathHeaderGrowth.java deleted file mode 100644 index 9a259672e8..0000000000 --- a/test/src/com/rabbitmq/client/test/server/XDeathHeaderGrowth.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.rabbitmq.client.test.server; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.test.BrokerTestCase; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -class RejectingConsumer extends DefaultConsumer { - private CountDownLatch latch; - private Map headers; - - public RejectingConsumer(Channel channel, CountDownLatch latch) { - super(channel); - this.latch = latch; - } - - @Override - public void handleDelivery(String consumerTag, Envelope envelope, - AMQP.BasicProperties properties, byte[] body) - throws IOException { - if(this.latch.getCount() > 0) { - this.getChannel().basicReject(envelope.getDeliveryTag(), false); - } else { - if(this.getChannel().isOpen()) { - this.getChannel().basicAck(envelope.getDeliveryTag(), false); - } - } - this.headers = properties.getHeaders(); - latch.countDown(); - } - - public Map getHeaders() { - return headers; - } -} - -public class XDeathHeaderGrowth extends BrokerTestCase { - @SuppressWarnings("unchecked") - public void testBoundedXDeathHeaderGrowth() throws IOException, InterruptedException { - final String x1 = "issues.rabbitmq-server-78.fanout1"; - declareTransientFanoutExchange(x1); - final String x2 = "issues.rabbitmq-server-78.fanout2"; - declareTransientFanoutExchange(x2); - final String x3 = "issues.rabbitmq-server-78.fanout3"; - declareTransientFanoutExchange(x3); - - final String q1 = "issues.rabbitmq-server-78.queue1"; - Map args1 = argumentsForDeadLetteringTo(x1); - declareTransientQueue(q1, args1); - - final String q2 = "issues.rabbitmq-server-78.queue2"; - Map args2 = argumentsForDeadLetteringTo(x2); - declareTransientQueue(q2, args2); - this.channel.queueBind(q2, x1, ""); - - final String q3 = "issues.rabbitmq-server-78.queue3"; - Map args3 = argumentsForDeadLetteringTo(x3); - declareTransientQueue(q3, args3); - this.channel.queueBind(q3, x2, ""); - - final String qz = "issues.rabbitmq-server-78.destination"; - Map args4 = argumentsForDeadLetteringTo(x3); - declareTransientQueue(qz, args4); - this.channel.queueBind(qz, x3, ""); - - CountDownLatch latch = new CountDownLatch(10); - RejectingConsumer cons = new RejectingConsumer(this.channel, latch); - this.channel.basicConsume(qz, cons); - - this.channel.basicPublish("", q1, null, "msg".getBytes()); - assertTrue(latch.await(5, TimeUnit.SECONDS)); - List> events = (List>)cons.getHeaders().get("x-death"); - assertEquals(4, events.size()); - - List qs = new ArrayList(); - for (Map evt : events) { - qs.add(evt.get("queue").toString()); - } - Collections.sort(qs); - assertEquals(Arrays.asList(qz, q1, q2, q3), qs); - List cs = new ArrayList(); - for (Map evt : events) { - cs.add((Long)evt.get("count")); - } - Collections.sort(cs); - assertEquals(Arrays.asList(1L, 1L, 1L, 9L), cs); - } - - private Map argumentsForDeadLetteringTo(String dlx) { - return argumentsForDeadLetteringTo(dlx, 1); - } - - private Map argumentsForDeadLetteringTo(String dlx, int ttl) { - Map m = new HashMap(); - m.put("x-dead-letter-exchange", dlx); - m.put("x-dead-letter-routing-key", "some-routing-key"); - m.put("x-message-ttl", ttl); - return m; - } -} diff --git a/test/src/com/rabbitmq/client/test/ssl/BadVerifiedConnection.java b/test/src/com/rabbitmq/client/test/ssl/BadVerifiedConnection.java deleted file mode 100644 index d525d0de4b..0000000000 --- a/test/src/com/rabbitmq/client/test/ssl/BadVerifiedConnection.java +++ /dev/null @@ -1,97 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test.ssl; - -import java.io.FileInputStream; -import java.io.IOException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.TrustManagerFactory; - -import com.rabbitmq.client.ConnectionFactory; - -/** - * Test for bug 19356 - SSL Support in rabbitmq - * - */ -public class BadVerifiedConnection extends UnverifiedConnection { - public void openConnection() - throws IOException - { - try { - String keystorePath = System.getProperty("keystore.empty.path"); - assertNotNull(keystorePath); - String keystorePasswd = System.getProperty("keystore.passwd"); - assertNotNull(keystorePasswd); - char [] keystorePassword = keystorePasswd.toCharArray(); - - KeyStore tks = KeyStore.getInstance("JKS"); - tks.load(new FileInputStream(keystorePath), keystorePassword); - - TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); - tmf.init(tks); - - String p12Path = System.getProperty("p12.path"); - assertNotNull(p12Path); - String p12Passwd = System.getProperty("p12.passwd"); - assertNotNull(p12Passwd); - KeyStore ks = KeyStore.getInstance("PKCS12"); - char [] p12Password = p12Passwd.toCharArray(); - ks.load(new FileInputStream(p12Path), p12Password); - - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); - kmf.init(ks, p12Password); - - SSLContext c = SSLContext.getInstance("SSLv3"); - c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - - connectionFactory = new ConnectionFactory(); - connectionFactory.useSslProtocol(c); - } catch (NoSuchAlgorithmException ex) { - throw new IOException(ex.toString()); - } catch (KeyManagementException ex) { - throw new IOException(ex.toString()); - } catch (KeyStoreException ex) { - throw new IOException(ex.toString()); - } catch (CertificateException ex) { - throw new IOException(ex.toString()); - } catch (UnrecoverableKeyException ex) { - throw new IOException(ex.toString()); - } - - if (connection == null) { - try { - connection = connectionFactory.newConnection(); - fail(); - } catch (SSLHandshakeException e) { - } catch (IOException e) { - fail(); - } - } - } - - public void openChannel() {} - public void testSSL() {} -} diff --git a/test/src/com/rabbitmq/client/test/ssl/SSLTests.java b/test/src/com/rabbitmq/client/test/ssl/SSLTests.java deleted file mode 100644 index 9df495670a..0000000000 --- a/test/src/com/rabbitmq/client/test/ssl/SSLTests.java +++ /dev/null @@ -1,31 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.client.test.ssl; - -import junit.framework.TestCase; -import junit.framework.TestSuite; - -public class SSLTests extends TestCase { - public static TestSuite suite() { - TestSuite suite = new TestSuite("ssl"); - suite.addTestSuite(UnverifiedConnection.class); - suite.addTestSuite(VerifiedConnection.class); - suite.addTestSuite(BadVerifiedConnection.class); - return suite; - } -} diff --git a/test/src/com/rabbitmq/client/test/ssl/UnverifiedConnection.java b/test/src/com/rabbitmq/client/test/ssl/UnverifiedConnection.java deleted file mode 100644 index abb10a996b..0000000000 --- a/test/src/com/rabbitmq/client/test/ssl/UnverifiedConnection.java +++ /dev/null @@ -1,59 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test.ssl; - -import java.io.IOException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; - -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.test.BrokerTestCase; - -/** - * Test for bug 19356 - SSL Support in rabbitmq - * - */ -public class UnverifiedConnection extends BrokerTestCase { - public void openConnection() - throws IOException - { - try { - connectionFactory.useSslProtocol(); - } catch (NoSuchAlgorithmException ex) { - throw new IOException(ex.toString()); - } catch (KeyManagementException ex) { - throw new IOException(ex.toString()); - } - - if (connection == null) { - connection = connectionFactory.newConnection(); - } - } - - public void testSSL() throws IOException - { - channel.queueDeclare("Bug19356Test", false, true, true, null); - channel.basicPublish("", "Bug19356Test", null, "SSL".getBytes()); - - GetResponse chResponse = channel.basicGet("Bug19356Test", false); - assertNotNull(chResponse); - - byte[] body = chResponse.getBody(); - assertEquals("SSL", new String(body)); - } - -} diff --git a/test/src/com/rabbitmq/client/test/ssl/VerifiedConnection.java b/test/src/com/rabbitmq/client/test/ssl/VerifiedConnection.java deleted file mode 100644 index ce4a4480e5..0000000000 --- a/test/src/com/rabbitmq/client/test/ssl/VerifiedConnection.java +++ /dev/null @@ -1,88 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.client.test.ssl; - -import java.io.FileInputStream; -import java.io.IOException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; - -import com.rabbitmq.client.ConnectionFactory; - -/** - * Test for bug 19356 - SSL Support in rabbitmq - * - */ -public class VerifiedConnection extends UnverifiedConnection { - - public void openConnection() - throws IOException - { - try { - String keystorePath = System.getProperty("keystore.path"); - assertNotNull(keystorePath); - String keystorePasswd = System.getProperty("keystore.passwd"); - assertNotNull(keystorePasswd); - char [] keystorePassword = keystorePasswd.toCharArray(); - - KeyStore tks = KeyStore.getInstance("JKS"); - tks.load(new FileInputStream(keystorePath), keystorePassword); - - TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); - tmf.init(tks); - - String p12Path = System.getProperty("p12.path"); - assertNotNull(p12Path); - String p12Passwd = System.getProperty("p12.passwd"); - assertNotNull(p12Passwd); - KeyStore ks = KeyStore.getInstance("PKCS12"); - char [] p12Password = p12Passwd.toCharArray(); - ks.load(new FileInputStream(p12Path), p12Password); - - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); - kmf.init(ks, p12Password); - - SSLContext c = SSLContext.getInstance("SSLv3"); - c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - - connectionFactory = new ConnectionFactory(); - connectionFactory.useSslProtocol(c); - } catch (NoSuchAlgorithmException ex) { - throw new IOException(ex.toString()); - } catch (KeyManagementException ex) { - throw new IOException(ex.toString()); - } catch (KeyStoreException ex) { - throw new IOException(ex.toString()); - } catch (CertificateException ex) { - throw new IOException(ex.toString()); - } catch (UnrecoverableKeyException ex) { - throw new IOException(ex.toString()); - } - - if (connection == null) { - connection = connectionFactory.newConnection(); - } - } -} diff --git a/test/src/com/rabbitmq/examples/BufferPerformanceMetrics.java b/test/src/com/rabbitmq/examples/BufferPerformanceMetrics.java deleted file mode 100644 index 61accc0533..0000000000 --- a/test/src/com/rabbitmq/examples/BufferPerformanceMetrics.java +++ /dev/null @@ -1,133 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import java.io.IOException; -import java.net.Socket; -import java.util.Random; - -import com.rabbitmq.client.AMQP.Queue; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.DefaultSocketConfigurator; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.QueueingConsumer; - - -/** - * Class to explore how performance of sending and receiving messages - * varies with the buffer size and enabling/disabling Nagle's - * algorithm. - */ -public class BufferPerformanceMetrics { - - public static final int MESSAGE_COUNT = 100000; - public static final byte[] MESSAGE = "".getBytes(); - public static final int REPEATS = 1000000; - public static final int PEAK_SIZE = 20 * 1024; - - public static final double NANOSECONDS_PER_SECOND = 1000 * 1000 * 1000; - - public static void main(String[] args) throws Exception { - final String uri = args.length > 0 ? args[0] : "amqp://localhost"; - - Random rnd = new Random(); - - System.out.println("buffer size, " + - "publish rate with nagle, " + - "consume rate with nagle, " + - "publish rate without nagle, " + - "consume rate without nagle"); - - for(int repeat = 0; repeat < REPEATS; repeat++) { - final int bufferSize = 1 + rnd.nextInt(PEAK_SIZE); - - double - publishRateNagle = 0, - publishRateNoNagle = 0, - consumeRateNagle = 0, - consumeRateNoNagle = 0; - - for(final boolean useNagle : new boolean[] { false, true }) { - ConnectionFactory factory = new ConnectionFactory() { - { - setUri(uri); - setSocketConfigurator(new DefaultSocketConfigurator() { - @Override - public void configure(Socket socket) throws IOException { - socket.setTcpNoDelay(!useNagle); - socket.setReceiveBufferSize(bufferSize); - socket.setSendBufferSize(bufferSize); - } - }); - } - }; - - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - Queue.DeclareOk res = channel.queueDeclare(); - String queueName = res.getQueue(); - - long start; - - start = System.nanoTime(); - - for(int i = 0; i < MESSAGE_COUNT; i++) { - channel.basicPublish("", queueName, - MessageProperties.BASIC, MESSAGE); - } - - QueueingConsumer consumer = new QueueingConsumer(channel); - channel.basicConsume(queueName, true, consumer); - - long publishTime = System.nanoTime() - start; - - start = System.nanoTime(); - - for(int i = 0; i < MESSAGE_COUNT; i++){ - consumer.nextDelivery(); - } - - long consumeTime = System.nanoTime() - start; - - double publishRate = - MESSAGE_COUNT / (publishTime / NANOSECONDS_PER_SECOND); - double consumeRate = - MESSAGE_COUNT / (consumeTime / NANOSECONDS_PER_SECOND); - - if(useNagle){ - publishRateNagle = publishRate; - consumeRateNagle = consumeRate; - } else { - publishRateNoNagle = publishRate; - consumeRateNoNagle = consumeRate; - } - - connection.close(); - // Small sleep to remove noise from hammering the server. - Thread.sleep(100); - } - - System.out.println(bufferSize + ", " + - publishRateNagle + ", " + - consumeRateNagle + ", " + - publishRateNoNagle + ", " + - consumeRateNoNagle); - } - } -} diff --git a/test/src/com/rabbitmq/examples/ChannelCreationPerformance.java b/test/src/com/rabbitmq/examples/ChannelCreationPerformance.java deleted file mode 100644 index 32dcf624f0..0000000000 --- a/test/src/com/rabbitmq/examples/ChannelCreationPerformance.java +++ /dev/null @@ -1,99 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import java.util.ArrayList; -import java.util.Collections; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; - -class ChannelCreationPerformance { - static Connection connect() throws Exception{ - return new ConnectionFactory() - {{setRequestedChannelMax(CHANNEL_MAX);}}.newConnection(); - } - - static final int CHANNEL_MAX = 10000; - static final int STEP = 1000; - static final int START = STEP; - - abstract static class PerformanceTest{ - final String name; - Connection c; - int i; - - PerformanceTest(String name){ - this.name = name; - } - - void run() throws Exception{ - System.out.println(name); - for(i = START; i <= CHANNEL_MAX ; i += STEP){ - c = connect(); - long start = System.currentTimeMillis(); - body(); - long time = System.currentTimeMillis() - start; - System.out.println(i + "\t" + time + " (" + (1000 * i / ((double)time)) + " channels/s)"); - c.close(); - } - } - - abstract void body() throws Exception; - - } - - public static void main(String[] args) throws Exception{ - new PerformanceTest("Sequential creation, no close:"){ - void body() throws Exception{ - for(int j = 1; j <= i; j++){ - c.createChannel(); - } - } - }.run(); - - new PerformanceTest("Sequential creation followed by close:"){ - void body() throws Exception{ - for(int j = 1; j <= i; j++){ - c.createChannel().close(); - } - } - }.run(); - - new PerformanceTest("Sequential creation then bulk close:"){ - void body() throws Exception{ - ArrayList channels = new ArrayList(); - for(int j = 1; j <= i; j++){ - channels.add(c.createChannel()); - } - for(Channel chan : channels) chan.close(); - } - }.run(); - - new PerformanceTest("Sequential creation then out of order bulk close:"){ - void body() throws Exception{ - ArrayList channels = new ArrayList(); - for(int j = 1; j <= i; j++){ - channels.add(c.createChannel()); - } - Collections.shuffle(channels); - for(Channel chan : channels) chan.close(); - } - }.run(); - } -} diff --git a/test/src/com/rabbitmq/examples/ConfirmDontLoseMessages.java b/test/src/com/rabbitmq/examples/ConfirmDontLoseMessages.java deleted file mode 100644 index 61c37d70af..0000000000 --- a/test/src/com/rabbitmq/examples/ConfirmDontLoseMessages.java +++ /dev/null @@ -1,108 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.examples; - -import java.io.IOException; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.QueueingConsumer; - -public class ConfirmDontLoseMessages { - static int msgCount = 10000; - final static String QUEUE_NAME = "confirm-test"; - static ConnectionFactory connectionFactory; - - public static void main(String[] args) - throws IOException, InterruptedException - { - if (args.length > 0) { - msgCount = Integer.parseInt(args[0]); - } - - connectionFactory = new ConnectionFactory(); - - // Consume msgCount messages. - (new Thread(new Consumer())).start(); - // Publish msgCount messages and wait for confirms. - (new Thread(new Publisher())).start(); - } - - @SuppressWarnings("ThrowablePrintedToSystemOut") - static class Publisher implements Runnable { - public void run() { - try { - long startTime = System.currentTimeMillis(); - - // Setup - Connection conn = connectionFactory.newConnection(); - Channel ch = conn.createChannel(); - ch.queueDeclare(QUEUE_NAME, true, false, false, null); - ch.confirmSelect(); - - // Publish - for (long i = 0; i < msgCount; ++i) { - ch.basicPublish("", QUEUE_NAME, - MessageProperties.PERSISTENT_BASIC, - "nop".getBytes()); - } - - ch.waitForConfirmsOrDie(); - - // Cleanup - ch.queueDelete(QUEUE_NAME); - ch.close(); - conn.close(); - - long endTime = System.currentTimeMillis(); - System.out.printf("Test took %.3fs\n", - (float)(endTime - startTime)/1000); - } catch (Throwable e) { - System.out.println("foobar :("); - System.out.print(e); - } - } - } - - static class Consumer implements Runnable { - public void run() { - try { - // Setup - Connection conn = connectionFactory.newConnection(); - Channel ch = conn.createChannel(); - ch.queueDeclare(QUEUE_NAME, true, false, false, null); - - // Consume - QueueingConsumer qc = new QueueingConsumer(ch); - ch.basicConsume(QUEUE_NAME, true, qc); - for (int i = 0; i < msgCount; ++i) { - qc.nextDelivery(); - } - - // Cleanup - ch.close(); - conn.close(); - } catch (Throwable e) { - System.out.println("Whoosh!"); - System.out.print(e); - } - } - } -} diff --git a/test/src/com/rabbitmq/examples/ConsumerMain.java b/test/src/com/rabbitmq/examples/ConsumerMain.java deleted file mode 100644 index 1ce702a61e..0000000000 --- a/test/src/com/rabbitmq/examples/ConsumerMain.java +++ /dev/null @@ -1,295 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.examples; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintStream; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.ShutdownSignalException; -import com.rabbitmq.utility.BlockingCell; - -public class ConsumerMain implements Runnable { - public static final int SUMMARY_EVERY_MS = 1000; - - public static final int ACK_BATCH_SIZE = 10; - - public static int optArg(String[] args, int index, int def) { - return (args.length > index) ? Integer.parseInt(args[index]) : def; - } - - public static String optArg(String[] args, int index, String def) { - return (args.length > index) ? args[index] : def; - } - - public static boolean optArg(String[] args, int index, boolean def) { - return (args.length > index) ? Boolean.valueOf(args[index]).booleanValue() : def; - } - - public static void main(String[] args) { - try { - final String uri = optArg(args, 0, "amqp://localhost"); - boolean writeStats = optArg(args, 1, true); - boolean autoAck = optArg(args, 2, true); - final Connection conn = new ConnectionFactory(){{setUri(uri);}}.newConnection(); - System.out.println("Channel 0 fully open."); - new ConsumerMain(conn, writeStats, autoAck).run(); - } catch (Exception e) { - System.err.println("Main thread caught exception: " + e); - e.printStackTrace(); - System.exit(1); - } - } - - public static void sleep(int ms) { - try { - Thread.sleep(ms); - } catch (InterruptedException ie) { - // no special error processing required - } - } - - public final Connection _connection; - - public final boolean _writeStats; - - public final boolean _autoAck; - - public ConsumerMain(Connection connection, boolean writeStats, boolean autoAck) { - _connection = connection; - _writeStats = writeStats; - _autoAck = autoAck; - System.out.println((_writeStats ? "WILL" : "WON'T") + " write statistics."); - System.out.println((_autoAck ? "WILL" : "WON'T") + " use server-side auto-acking."); - } - - public void run() { - try { - runIt(); - } catch (IOException ex) { - System.err.println("hit IOException in ConsumerMain: trace follows"); - ex.printStackTrace(); - throw new RuntimeException(ex); - } - } - - private void runIt() throws IOException { - Channel channel = _connection.createChannel(); - - String queueName = "test queue"; - channel.queueDeclare(queueName, true, false, false, null); - - String exchangeName = "test completion"; - channel.exchangeDeclare(exchangeName, "fanout", false, false, null); - - String completionQueue = channel.queueDeclare().getQueue(); - channel.queueBind(completionQueue, exchangeName, ""); - - LatencyExperimentConsumer callback = new LatencyExperimentConsumer(channel, queueName); - callback._autoAck = this._autoAck; - - channel.basicConsume(queueName, _autoAck, callback); - channel.basicConsume(completionQueue, true, "completion", callback); - callback.report(_writeStats); - - System.out.println("Deleting test queue."); - channel.queueDelete(queueName); - - System.out.println("Deleting completion queue."); - channel.queueDelete(completionQueue); - - System.out.println("Closing the channel."); - channel.close(); - - System.out.println("Closing the connection."); - _connection.close(); - - System.out.println("Leaving ConsumerMain.run()."); - } - - public static class LatencyExperimentConsumer extends DefaultConsumer { - - public final String _queueName; - - public long _startTime; - - public long _mostRecentTime; - - public int _received; - - public int _previousReceived; - - public long _previousReportTime; - - public long[] _deltas; - - public final BlockingCell _blocker; - - public long _nextSummaryTime; - - public boolean _autoAck = true; - - public LatencyExperimentConsumer(Channel ch, String queueName) { - super(ch); - _queueName = queueName; - _received = 0; - _previousReceived = 0; - _deltas = null; - _blocker = new BlockingCell(); - } - - public void report(boolean writeStats) throws IOException { - Object sentinel = _blocker.uninterruptibleGet(); - if (sentinel instanceof ShutdownSignalException) { - System.out.println("Aborted with shutdown signal in consumer."); - System.exit(1); - } - - long totalDelta = _mostRecentTime - _startTime; - - long maxL, minL; - double sumL; - - maxL = Long.MIN_VALUE; - minL = Long.MAX_VALUE; - sumL = 0.0; - - int messageCount = _received; - - for (int i = 0; i < messageCount; i++) { - long v = _deltas[i]; - if (v > maxL) - maxL = v; - if (v < minL) - minL = v; - sumL += v; - } - - System.out.println("CONSUMER - Overall: " - + String.format("%d messages in %dms, a rate of %.2f msgs/sec", messageCount, - totalDelta, - (messageCount / (totalDelta / 1000.0)))); - System.out.println("Latency - Min (Avg) Max: " - + String.format("%dms (%.2fms) %dms", minL, sumL - / messageCount, maxL)); - - if (writeStats) { - PrintStream o = new PrintStream(new FileOutputStream("simple-latency-experiment.csv")); - for (int i = 0; i < messageCount; i++) { - o.println(i + "," + _deltas[i]); - } - o.close(); - - int[] bins = new int[(int) maxL + 1]; - for (int i = 0; i < messageCount; i++) { - if (_deltas[i] != 0) { - bins[(int) _deltas[i]]++; - } - } - - o = new PrintStream(new FileOutputStream("simple-latency-bins.csv")); - for (int i = 0; i < bins.length; i++) { - o.println(i + "," + bins[i]); - } - o.close(); - } - } - - @Override public void handleShutdownSignal(String consumerTag, - ShutdownSignalException sig) - { - System.out.println("Shutdown signal terminating consumer " + consumerTag + - " with signal " + sig); - if (sig.getCause() != null) { - sig.printStackTrace(); - } - _blocker.setIfUnset(sig); - } - - @Override public void handleDelivery(String consumerTag, - Envelope envelope, - AMQP.BasicProperties properties, - byte[] body) - throws IOException - { - if ("completion".equals(consumerTag)) { - System.out.println("Got completion message."); - finish(); - return; - } - - if (body.length == 0) { - return; - } - - long now = System.currentTimeMillis(); - DataInputStream d = new DataInputStream(new ByteArrayInputStream(body)); - int messagesRemaining = d.readInt(); - long msgStartTime = d.readLong(); - - _mostRecentTime = System.currentTimeMillis(); - - if (_deltas == null) { - _startTime = now; - _previousReportTime = _startTime; - _nextSummaryTime = _startTime + SUMMARY_EVERY_MS; - _deltas = new long[messagesRemaining + 1]; - } - - if (msgStartTime != -1) { - _deltas[_received++] = now - msgStartTime; - - if (!_autoAck && ((_received % ACK_BATCH_SIZE) == 0)) { - getChannel().basicAck(0, true); - } - } - - if (now > _nextSummaryTime) { - summariseProgress(now); - _nextSummaryTime += SUMMARY_EVERY_MS; - } - - if (messagesRemaining == 0) { - finish(); - } - } - - public void finish() throws IOException { - if (!_autoAck) - getChannel().basicAck(0, true); - _blocker.setIfUnset(new Object()); - } - - public void summariseProgress(long now) { - int countOverInterval = _received - _previousReceived; - double intervalRate = countOverInterval / ((now - _previousReportTime) / 1000.0); - _previousReceived = _received; - _previousReportTime = now; - System.out.println((now - _startTime) + " ms: Received " + _received + " - " + countOverInterval + " since last report (" + (int) intervalRate - + " Hz)"); - } - } -} diff --git a/test/src/com/rabbitmq/examples/DirectReplyToPerformance.java b/test/src/com/rabbitmq/examples/DirectReplyToPerformance.java deleted file mode 100644 index 5fc8c9a85c..0000000000 --- a/test/src/com/rabbitmq/examples/DirectReplyToPerformance.java +++ /dev/null @@ -1,208 +0,0 @@ -package com.rabbitmq.examples; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Consumer; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.ShutdownSignalException; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; - -public class DirectReplyToPerformance { - private static final String DIRECT_QUEUE = "amq.rabbitmq.reply-to"; - private static final String SERVER_QUEUE = "server-queue"; - private static final int CLIENTS = 5; - private static final int RPC_COUNT_PER_CLIENT = 2000; - - public static void main(String[] args) throws Exception { - String uri = args[0]; - start(new Server(uri)); - - doTest(uri, DirectReply.class, true); - doTest(uri, SharedReplyQueue.class, true); - doTest(uri, PerRPCReplyQueue.class, true); - doTest(uri, DirectReply.class, false); - doTest(uri, SharedReplyQueue.class, false); - doTest(uri, PerRPCReplyQueue.class, false); - System.exit(0); - } - - private static void doTest(String uri, Class strategy, boolean reuseConnection) throws Exception { - System.out.println("*** " + strategy.getSimpleName() + (reuseConnection ? " (reusing connections)" : "")); - CountDownLatch latch = new CountDownLatch(CLIENTS); - for (int i = 0; i < CLIENTS; i++) { - start(new Client(uri, latch, (ReplyQueueStrategy) strategy.newInstance(), reuseConnection)); - } - latch.await(); - } - - private static void start(final Task task) { - new Thread(new Runnable() { - public void run() { - try { - task.run(); - } catch (Exception e) { - System.out.println(e.getMessage()); - e.printStackTrace(); - System.exit(1); - } - } - }).start(); - } - - private interface Task { - public void run() throws Exception; - } - - private interface ReplyQueueStrategy { - public String preMsg(Channel ch, Consumer consumer) throws IOException; - public void postMsg(Channel ch) throws IOException; - } - - public static class DirectReply implements ReplyQueueStrategy { - private String ctag; - - public String preMsg(Channel ch, Consumer consumer) throws IOException { - ctag = ch.basicConsume(DIRECT_QUEUE, true, consumer); - return DIRECT_QUEUE; - } - - public void postMsg(Channel ch) throws IOException { - ch.basicCancel(ctag); - } - } - - public static class SharedReplyQueue implements ReplyQueueStrategy { - private final String queue; - private String ctag; - - public SharedReplyQueue() { - queue = "reply-queue-" + UUID.randomUUID(); - } - - public String preMsg(Channel ch, Consumer consumer) throws IOException { - Map args = new HashMap(); - args.put("x-expires", 10000); - ch.queueDeclare(queue, false, false, false, args); - ctag = ch.basicConsume(queue, true, consumer); - return queue; - } - - public void postMsg(Channel ch) throws IOException { - ch.basicCancel(ctag); - } - } - - public static class PerRPCReplyQueue implements ReplyQueueStrategy { - private String queue; - - public String preMsg(Channel ch, Consumer consumer) throws IOException { - queue = ch.queueDeclare().getQueue(); - ch.basicConsume(queue, true, consumer); - return queue; - } - - public void postMsg(Channel ch) throws IOException { - ch.queueDelete(queue); - } - } - private static class Server implements Task { - private final String uri; - - public Server(String uri) { - this.uri = uri; - } - - public void run() throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setUri(uri); - Connection connection = factory.newConnection(); - final Channel ch = connection.createChannel(); - ch.queueDeclare(SERVER_QUEUE, false, true, false, null); - ch.basicConsume(SERVER_QUEUE, true, new DefaultConsumer(ch) { - @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { - String replyTo = properties.getReplyTo(); - ch.basicPublish("", replyTo, MessageProperties.MINIMAL_BASIC, "Hello client!".getBytes()); - } - }); - } - } - - private static class Client implements Task { - private final String uri; - private final CountDownLatch globalLatch; - private final ReplyQueueStrategy strategy; - private final boolean reuseConnection; - - public Client(String uri, CountDownLatch latch, ReplyQueueStrategy strategy, boolean reuseConnection) { - this.uri = uri; - this.globalLatch = latch; - this.strategy = strategy; - this.reuseConnection = reuseConnection; - } - - public void run() throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - factory.setUri(uri); - final CountDownLatch[] latch = new CountDownLatch[1]; - long time = System.nanoTime(); - Consumer cons = new ClientConsumer(latch); - Connection conn = null; - Channel ch = null; - if (reuseConnection) { - conn = factory.newConnection(); - ch = conn.createChannel(); - } - for (int i = 0; i < RPC_COUNT_PER_CLIENT; i++) { - latch[0] = new CountDownLatch(1); - if (!reuseConnection) { - conn = factory.newConnection(); - ch = conn.createChannel(); - } - - String replyTo = strategy.preMsg(ch, cons); - AMQP.BasicProperties props = MessageProperties.MINIMAL_BASIC.builder().replyTo(replyTo).build(); - ch.basicPublish("", SERVER_QUEUE, props, "Hello server!".getBytes()); - latch[0].await(); - strategy.postMsg(ch); - if (!reuseConnection) { - conn.close(); - } - } - if (reuseConnection) { - conn.close(); - } - System.out.println((System.nanoTime() - time) / (1000 * RPC_COUNT_PER_CLIENT) + "us per RPC"); - globalLatch.countDown(); - } - } - - private static class ClientConsumer implements Consumer { - private final CountDownLatch[] latch; - - public ClientConsumer(CountDownLatch[] latch) { - this.latch = latch; - } - - @Override public void handleConsumeOk(String consumerTag) {} - @Override public void handleCancelOk(String consumerTag) {} - @Override public void handleCancel(String consumerTag) throws IOException {} - @Override public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) {} - @Override public void handleRecoverOk(String consumerTag) {} - - @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { - latch[0].countDown(); - } - } - -} diff --git a/test/src/com/rabbitmq/examples/FileConsumer.java b/test/src/com/rabbitmq/examples/FileConsumer.java deleted file mode 100644 index 2a3602b48a..0000000000 --- a/test/src/com/rabbitmq/examples/FileConsumer.java +++ /dev/null @@ -1,123 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.examples; - -import java.io.File; -import java.io.FileOutputStream; -import java.util.Map; -import java.util.UUID; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.GnuParser; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.QueueingConsumer; - -public class FileConsumer { - public static void main(String[] args) { - Options options = new Options(); - options.addOption(new Option("h", "uri", true, "AMQP URI")); - options.addOption(new Option("q", "queue", true, "queue name")); - options.addOption(new Option("t", "type", true, "exchange type")); - options.addOption(new Option("e", "exchange", true, "exchange name")); - options.addOption(new Option("k", "routing-key", true, "routing key")); - options.addOption(new Option("d", "directory", true, "output directory")); - - CommandLineParser parser = new GnuParser(); - - try { - CommandLine cmd = parser.parse(options, args); - - String uri = strArg(cmd, 'h', "amqp://localhost"); - String requestedQueueName = strArg(cmd, 'q', ""); - String exchangeType = strArg(cmd, 't', "direct"); - String exchange = strArg(cmd, 'e', null); - String routingKey = strArg(cmd, 'k', null); - String outputDirName = strArg(cmd, 'd', "."); - - File outputDir = new File(outputDirName); - if (!outputDir.exists() || !outputDir.isDirectory()) { - System.err.println("Output directory must exist, and must be a directory."); - System.exit(2); - } - - ConnectionFactory connFactory = new ConnectionFactory(); - connFactory.setUri(uri); - Connection conn = connFactory.newConnection(); - - final Channel ch = conn.createChannel(); - - String queueName = - (requestedQueueName.equals("") - ? ch.queueDeclare() - : ch.queueDeclare(requestedQueueName, - false, false, false, null)).getQueue(); - - if (exchange != null || routingKey != null) { - if (exchange == null) { - System.err.println("Please supply exchange name to bind to (-e)"); - System.exit(2); - } - if (routingKey == null) { - System.err.println("Please supply routing key pattern to bind to (-k)"); - System.exit(2); - } - ch.exchangeDeclare(exchange, exchangeType); - ch.queueBind(queueName, exchange, routingKey); - } - - QueueingConsumer consumer = new QueueingConsumer(ch); - ch.basicConsume(queueName, consumer); - while (true) { - QueueingConsumer.Delivery delivery = consumer.nextDelivery(); - Map headers = delivery.getProperties().getHeaders(); - byte[] body = delivery.getBody(); - Object headerFilenameO = headers.get("filename"); - String headerFilename = - (headerFilenameO == null) - ? UUID.randomUUID().toString() - : headerFilenameO.toString(); - File givenName = new File(headerFilename); - if (givenName.getName().equals("")) { - System.out.println("Skipping file with empty name: " + givenName); - } else { - File f = new File(outputDir, givenName.getName()); - System.out.print("Writing " + f + " ..."); - FileOutputStream o = new FileOutputStream(f); - o.write(body); - o.close(); - System.out.println(" done."); - } - ch.basicAck(delivery.getEnvelope().getDeliveryTag(), false); - } - } catch (Exception ex) { - System.err.println("Main thread caught exception: " + ex); - ex.printStackTrace(); - System.exit(1); - } - } - - private static String strArg(CommandLine cmd, char opt, String def) { - return cmd.getOptionValue(opt, def); - } -} diff --git a/test/src/com/rabbitmq/examples/FileProducer.java b/test/src/com/rabbitmq/examples/FileProducer.java deleted file mode 100644 index fe6bb9eb00..0000000000 --- a/test/src/com/rabbitmq/examples/FileProducer.java +++ /dev/null @@ -1,98 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.examples; - -import java.io.File; -import java.io.FileInputStream; -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.GnuParser; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.AMQP.BasicProperties; - -public class FileProducer { - public static void main(String[] args) { - Options options = new Options(); - options.addOption(new Option("h", "uri", true, "AMQP URI")); - options.addOption(new Option("p", "port", true, "broker port")); - options.addOption(new Option("t", "type", true, "exchange type")); - options.addOption(new Option("e", "exchange", true, "exchange name")); - options.addOption(new Option("k", "routing-key", true, "routing key")); - - CommandLineParser parser = new GnuParser(); - - try { - CommandLine cmd = parser.parse(options, args); - - String uri = strArg(cmd, 'h', "amqp://localhost"); - String exchangeType = strArg(cmd, 't', "direct"); - String exchange = strArg(cmd, 'e', null); - String routingKey = strArg(cmd, 'k', null); - - ConnectionFactory connFactory = new ConnectionFactory(); - connFactory.setUri(uri); - Connection conn = connFactory.newConnection(); - - final Channel ch = conn.createChannel(); - - if (exchange == null) { - System.err.println("Please supply exchange name to send to (-e)"); - System.exit(2); - } - if (routingKey == null) { - System.err.println("Please supply routing key to send to (-k)"); - System.exit(2); - } - ch.exchangeDeclare(exchange, exchangeType); - - for (String filename : cmd.getArgs()) { - System.out.print("Sending " + filename + "..."); - File f = new File(filename); - FileInputStream i = new FileInputStream(f); - byte[] body = new byte[(int) f.length()]; - i.read(body); - i.close(); - - Map headers = new HashMap(); - headers.put("filename", filename); - headers.put("length", (int) f.length()); - BasicProperties props = new BasicProperties.Builder().headers(headers).build(); - ch.basicPublish(exchange, routingKey, props, body); - System.out.println(" done."); - } - - conn.close(); - } catch (Exception ex) { - System.err.println("Main thread caught exception: " + ex); - ex.printStackTrace(); - System.exit(1); - } - } - - private static String strArg(CommandLine cmd, char opt, String def) { - return cmd.getOptionValue(opt, def); - } -} diff --git a/test/src/com/rabbitmq/examples/HelloClient.java b/test/src/com/rabbitmq/examples/HelloClient.java deleted file mode 100644 index e21b597d9e..0000000000 --- a/test/src/com/rabbitmq/examples/HelloClient.java +++ /dev/null @@ -1,44 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.RpcClient; - -public class HelloClient { - public static void main(String[] args) { - try { - String request = (args.length > 0) ? args[0] : "Rabbit"; - String uri = (args.length > 1) ? args[1] : "amqp://localhost"; - - ConnectionFactory cfconn = new ConnectionFactory(); - cfconn.setUri(uri); - Connection conn = cfconn.newConnection(); - Channel ch = conn.createChannel(); - RpcClient service = new RpcClient(ch, "", "Hello"); - - System.out.println(service.stringCall(request)); - conn.close(); - } catch (Exception e) { - System.err.println("Main thread caught exception: " + e); - e.printStackTrace(); - System.exit(1); - } - } -} diff --git a/test/src/com/rabbitmq/examples/HelloJsonClient.java b/test/src/com/rabbitmq/examples/HelloJsonClient.java deleted file mode 100644 index dbed068004..0000000000 --- a/test/src/com/rabbitmq/examples/HelloJsonClient.java +++ /dev/null @@ -1,55 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.tools.jsonrpc.JsonRpcClient; - -public class HelloJsonClient { - private static final int RPC_TIMEOUT_ONE_SECOND = 1000; - - public static void main(String[] args) { - try { - String request = (args.length > 0) ? args[0] : "Rabbit"; - String uri = (args.length > 1) ? args[1] : "amqp://localhost"; - - ConnectionFactory cfconn = new ConnectionFactory(); - cfconn.setUri(uri); - Connection conn = cfconn.newConnection(); - Channel ch = conn.createChannel(); - JsonRpcClient client = new JsonRpcClient(ch, "", "Hello", RPC_TIMEOUT_ONE_SECOND); - HelloJsonService service = - (HelloJsonService) client.createProxy(HelloJsonService.class); - - System.out.println(service.greeting(request)); - - java.util.List numbers = new java.util.ArrayList(); - numbers.add(1); - numbers.add(2); - numbers.add(3); - System.out.println("1 + 2 + 3 = " + service.sum(numbers)); - - conn.close(); - } catch (Exception e) { - System.err.println("Main thread caught exception: " + e); - e.printStackTrace(); - System.exit(1); - } - } -} diff --git a/test/src/com/rabbitmq/examples/HelloJsonServer.java b/test/src/com/rabbitmq/examples/HelloJsonServer.java deleted file mode 100644 index 19329a40ab..0000000000 --- a/test/src/com/rabbitmq/examples/HelloJsonServer.java +++ /dev/null @@ -1,54 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.tools.jsonrpc.JsonRpcServer; - -public class HelloJsonServer { - public static void main(String[] args) { - try { - String uri = (args.length > 0) ? args[0] : "amqp://localhost"; - - ConnectionFactory connFactory = new ConnectionFactory(); - connFactory.setUri(uri); - Connection conn = connFactory.newConnection(); - final Channel ch = conn.createChannel(); - - ch.queueDeclare("Hello", false, false, false, null); - JsonRpcServer server = - new JsonRpcServer(ch, "Hello", HelloJsonService.class, - new HelloJsonService() { - public String greeting(String name) { - return "Hello, "+name+", from JSON-RPC over AMQP!"; - } - public int sum(java.util.List args) { - int s = 0; - for (int i: args) { s += i; } - return s; - } - }); - server.mainloop(); - } catch (Exception ex) { - System.err.println("Main thread caught exception: " + ex); - ex.printStackTrace(); - System.exit(1); - } - } -} diff --git a/test/src/com/rabbitmq/examples/HelloJsonService.java b/test/src/com/rabbitmq/examples/HelloJsonService.java deleted file mode 100644 index 77d45ff084..0000000000 --- a/test/src/com/rabbitmq/examples/HelloJsonService.java +++ /dev/null @@ -1,24 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.examples; - -public interface HelloJsonService { - String greeting(String name); - - int sum(java.util.List values); -} diff --git a/test/src/com/rabbitmq/examples/HelloServer.java b/test/src/com/rabbitmq/examples/HelloServer.java deleted file mode 100644 index 703132e375..0000000000 --- a/test/src/com/rabbitmq/examples/HelloServer.java +++ /dev/null @@ -1,52 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.examples; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.StringRpcServer; - -public class HelloServer { - public static void main(String[] args) { - try { - String hostName = (args.length > 0) ? args[0] : "localhost"; - int portNumber = (args.length > 1) ? Integer.parseInt(args[1]) : AMQP.PROTOCOL.PORT; - - ConnectionFactory connFactory = new ConnectionFactory(); - connFactory.setHost(hostName); - connFactory.setPort(portNumber); - Connection conn = connFactory.newConnection(); - final Channel ch = conn.createChannel(); - - ch.queueDeclare("Hello", false, false, false, null); - StringRpcServer server = new StringRpcServer(ch, "Hello") { - public String handleStringCall(String request) { - System.out.println("Got request: " + request); - return "Hello, " + request + "!"; - } - }; - server.mainloop(); - } catch (Exception ex) { - System.err.println("Main thread caught exception: " + ex); - ex.printStackTrace(); - System.exit(1); - } - } -} diff --git a/test/src/com/rabbitmq/examples/LogTail.java b/test/src/com/rabbitmq/examples/LogTail.java deleted file mode 100644 index b2d39a220b..0000000000 --- a/test/src/com/rabbitmq/examples/LogTail.java +++ /dev/null @@ -1,57 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.QueueingConsumer; - -public class LogTail { - public static void main(String[] args) { - try { - String uri = (args.length > 0) ? args[0] : "amqp://localhost"; - String exchange = (args.length > 1) ? args[1] : "amq.rabbitmq.log"; - - ConnectionFactory cfconn = new ConnectionFactory(); - cfconn.setUri(uri); - Connection conn = cfconn.newConnection(); - - Channel ch1 = conn.createChannel(); - - String queueName = ch1.queueDeclare().getQueue(); - ch1.queueBind(queueName, exchange, "#"); - - QueueingConsumer consumer = new QueueingConsumer(ch1); - ch1.basicConsume(queueName, true, consumer); - while (true) { - QueueingConsumer.Delivery delivery = consumer.nextDelivery(); - String routingKey = delivery.getEnvelope().getRoutingKey(); - String contentType = delivery.getProperties().getContentType(); - System.out.println("Content-type: " + contentType); - System.out.println("Routing-key: " + routingKey); - System.out.println("Body:"); - System.out.println(new String(delivery.getBody())); - System.out.println(); - } - } catch (Exception e) { - System.err.println("Main thread caught exception: " + e); - e.printStackTrace(); - System.exit(1); - } - } -} diff --git a/test/src/com/rabbitmq/examples/MulticastMain.java b/test/src/com/rabbitmq/examples/MulticastMain.java deleted file mode 100644 index d33621116c..0000000000 --- a/test/src/com/rabbitmq/examples/MulticastMain.java +++ /dev/null @@ -1,34 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -/** - * TODO: delete this after a suitable time has passed - */ -public class MulticastMain { - public static void main(String[] args) { - System.out.println(); - System.out.println("********************************************************"); - System.out.println("* NOTE: *"); - System.out.println("* com.rabbitmq.examples.MulticastMain is now known as *"); - System.out.println("* com.rabbitmq.examples.PerfTest *"); - System.out.println("* *"); - System.out.println("********************************************************"); - System.out.println(); - System.exit(1); - } -} diff --git a/test/src/com/rabbitmq/examples/PerQueueTTLGetter.java b/test/src/com/rabbitmq/examples/PerQueueTTLGetter.java deleted file mode 100644 index 8705ae0c4a..0000000000 --- a/test/src/com/rabbitmq/examples/PerQueueTTLGetter.java +++ /dev/null @@ -1,46 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.GetResponse; - -/** - */ -public class PerQueueTTLGetter { - - public static void main(String[] args) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - - String queue = "ttl.queue"; - - // exchange - GetResponse response = channel.basicGet(queue, false); - if(response == null) { - System.out.println("Got no message..."); - } else { - System.out.println("Got message: " + new String(response.getBody())); - channel.basicAck(response.getEnvelope().getDeliveryTag(), false); - } - channel.close(); - connection.close(); - } -} diff --git a/test/src/com/rabbitmq/examples/PerQueueTTLPublisher.java b/test/src/com/rabbitmq/examples/PerQueueTTLPublisher.java deleted file mode 100644 index 533abdd0e2..0000000000 --- a/test/src/com/rabbitmq/examples/PerQueueTTLPublisher.java +++ /dev/null @@ -1,55 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; - -import java.util.Collections; - -/** - */ -public class PerQueueTTLPublisher { - - public static void main(String[] args) throws Exception { - ConnectionFactory factory = new ConnectionFactory(); - Connection connection = factory.newConnection(); - Channel channel = connection.createChannel(); - - String exchange = "ttl.exchange"; - String queue = "ttl.queue"; - - // exchange - channel.exchangeDeclare(exchange, "direct"); - - // queue - channel.queueDeclare(queue, true, false, false, Collections.singletonMap("x-message-ttl", (Object) 30000L)); - channel.queueBind(queue, exchange, queue, null); - - // send a message - AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().deliveryMode(2).build(); - for(int x = 0; x < 10; x++) { - channel.basicPublish(exchange, queue, props, ("Msg [" + x + "]").getBytes()); - } - - System.out.println("Done"); - channel.close(); - connection.close(); - } -} diff --git a/test/src/com/rabbitmq/examples/PerfTest.java b/test/src/com/rabbitmq/examples/PerfTest.java deleted file mode 100644 index 6381e6ecdf..0000000000 --- a/test/src/com/rabbitmq/examples/PerfTest.java +++ /dev/null @@ -1,255 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import java.util.Arrays; -import java.util.List; - -import com.rabbitmq.examples.perf.MulticastParams; -import com.rabbitmq.examples.perf.MulticastSet; -import com.rabbitmq.examples.perf.Stats; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.GnuParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; - -import com.rabbitmq.client.ConnectionFactory; - - -public class PerfTest { - public static void main(String[] args) { - Options options = getOptions(); - CommandLineParser parser = new GnuParser(); - try { - CommandLine cmd = parser.parse(options, args); - - if (cmd.hasOption('?')) { - usage(options); - System.exit(0); - } - - String exchangeType = strArg(cmd, 't', "direct"); - String exchangeName = strArg(cmd, 'e', exchangeType); - String queueName = strArg(cmd, 'u', ""); - String routingKey = strArg(cmd, 'k', null); - boolean randomRoutingKey = cmd.hasOption('K'); - int samplingInterval = intArg(cmd, 'i', 1); - float producerRateLimit = floatArg(cmd, 'r', 0.0f); - float consumerRateLimit = floatArg(cmd, 'R', 0.0f); - int producerCount = intArg(cmd, 'x', 1); - int consumerCount = intArg(cmd, 'y', 1); - int producerTxSize = intArg(cmd, 'm', 0); - int consumerTxSize = intArg(cmd, 'n', 0); - long confirm = intArg(cmd, 'c', -1); - boolean autoAck = cmd.hasOption('a'); - int multiAckEvery = intArg(cmd, 'A', 0); - int channelPrefetch = intArg(cmd, 'Q', 0); - int consumerPrefetch = intArg(cmd, 'q', 0); - int minMsgSize = intArg(cmd, 's', 0); - int timeLimit = intArg(cmd, 'z', 0); - int producerMsgCount = intArg(cmd, 'C', 0); - int consumerMsgCount = intArg(cmd, 'D', 0); - List flags = lstArg(cmd, 'f'); - int frameMax = intArg(cmd, 'M', 0); - int heartbeat = intArg(cmd, 'b', 0); - boolean predeclared = cmd.hasOption('p'); - - String uri = strArg(cmd, 'h', "amqp://localhost"); - - //setup - PrintlnStats stats = new PrintlnStats(1000L * samplingInterval, - producerCount > 0, - consumerCount > 0, - (flags.contains("mandatory") || - flags.contains("immediate")), - confirm != -1); - - ConnectionFactory factory = new ConnectionFactory(); - factory.setShutdownTimeout(0); // So we still shut down even with slow consumers - factory.setUri(uri); - factory.setRequestedFrameMax(frameMax); - factory.setRequestedHeartbeat(heartbeat); - - - MulticastParams p = new MulticastParams(); - p.setAutoAck( autoAck); - p.setAutoDelete( true); - p.setConfirm( confirm); - p.setConsumerCount( consumerCount); - p.setConsumerMsgCount( consumerMsgCount); - p.setConsumerRateLimit(consumerRateLimit); - p.setConsumerTxSize( consumerTxSize); - p.setExchangeName( exchangeName); - p.setExchangeType( exchangeType); - p.setFlags( flags); - p.setMultiAckEvery( multiAckEvery); - p.setMinMsgSize( minMsgSize); - p.setPredeclared( predeclared); - p.setConsumerPrefetch( consumerPrefetch); - p.setChannelPrefetch( channelPrefetch); - p.setProducerCount( producerCount); - p.setProducerMsgCount( producerMsgCount); - p.setProducerTxSize( producerTxSize); - p.setQueueName( queueName); - p.setRoutingKey( routingKey); - p.setRandomRoutingKey( randomRoutingKey); - p.setProducerRateLimit(producerRateLimit); - p.setTimeLimit( timeLimit); - - MulticastSet set = new MulticastSet(stats, factory, p); - set.run(true); - - stats.printFinal(); - } - catch( ParseException exp ) { - System.err.println("Parsing failed. Reason: " + exp.getMessage()); - usage(options); - } catch (Exception e) { - System.err.println("Main thread caught exception: " + e); - e.printStackTrace(); - System.exit(1); - } - } - - private static void usage(Options options) { - HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp("", options); - } - - private static Options getOptions() { - Options options = new Options(); - options.addOption(new Option("?", "help", false,"show usage")); - options.addOption(new Option("h", "uri", true, "AMQP URI")); - options.addOption(new Option("t", "type", true, "exchange type")); - options.addOption(new Option("e", "exchange", true, "exchange name")); - options.addOption(new Option("u", "queue", true, "queue name")); - options.addOption(new Option("k", "routingKey", true, "routing key")); - options.addOption(new Option("K", "randomRoutingKey", false,"use random routing key per message")); - options.addOption(new Option("i", "interval", true, "sampling interval")); - options.addOption(new Option("r", "rate", true, "producer rate limit")); - options.addOption(new Option("R", "consumerRate", true, "consumer rate limit")); - options.addOption(new Option("x", "producers", true, "producer count")); - options.addOption(new Option("y", "consumers", true, "consumer count")); - options.addOption(new Option("m", "ptxsize", true, "producer tx size")); - options.addOption(new Option("n", "ctxsize", true, "consumer tx size")); - options.addOption(new Option("c", "confirm", true, "max unconfirmed publishes")); - options.addOption(new Option("a", "autoack", false,"auto ack")); - options.addOption(new Option("A", "multiAckEvery", true, "multi ack every")); - options.addOption(new Option("q", "qos", true, "consumer prefetch count")); - options.addOption(new Option("Q", "globalQos", true, "channel prefetch count")); - options.addOption(new Option("s", "size", true, "message size")); - options.addOption(new Option("z", "time", true, "time limit")); - options.addOption(new Option("C", "pmessages", true, "producer message count")); - options.addOption(new Option("D", "cmessages", true, "consumer message count")); - Option flag = new Option("f", "flag", true, "message flag"); - flag.setArgs(Option.UNLIMITED_VALUES); - options.addOption(flag); - options.addOption(new Option("M", "framemax", true, "frame max")); - options.addOption(new Option("b", "heartbeat", true, "heartbeat interval")); - options.addOption(new Option("p", "predeclared", false,"allow use of predeclared objects")); - return options; - } - - private static String strArg(CommandLine cmd, char opt, String def) { - return cmd.getOptionValue(opt, def); - } - - private static int intArg(CommandLine cmd, char opt, int def) { - return Integer.parseInt(cmd.getOptionValue(opt, Integer.toString(def))); - } - - private static float floatArg(CommandLine cmd, char opt, float def) { - return Float.parseFloat(cmd.getOptionValue(opt, Float.toString(def))); - } - - private static List lstArg(CommandLine cmd, char opt) { - String[] vals = cmd.getOptionValues('f'); - if (vals == null) { - vals = new String[] {}; - } - return Arrays.asList(vals); - } - - private static class PrintlnStats extends Stats { - private final boolean sendStatsEnabled; - private final boolean recvStatsEnabled; - private final boolean returnStatsEnabled; - private final boolean confirmStatsEnabled; - - public PrintlnStats(long interval, - boolean sendStatsEnabled, boolean recvStatsEnabled, - boolean returnStatsEnabled, boolean confirmStatsEnabled) { - super(interval); - this.sendStatsEnabled = sendStatsEnabled; - this.recvStatsEnabled = recvStatsEnabled; - this.returnStatsEnabled = returnStatsEnabled; - this.confirmStatsEnabled = confirmStatsEnabled; - } - - @Override - protected void report(long now) { - System.out.print("time: " + String.format("%.3f", (now - startTime)/1000.0) + "s"); - - showRate("sent", sendCountInterval, sendStatsEnabled, elapsedInterval); - showRate("returned", returnCountInterval, sendStatsEnabled && returnStatsEnabled, elapsedInterval); - showRate("confirmed", confirmCountInterval, sendStatsEnabled && confirmStatsEnabled, elapsedInterval); - showRate("nacked", nackCountInterval, sendStatsEnabled && confirmStatsEnabled, elapsedInterval); - showRate("received", recvCountInterval, recvStatsEnabled, elapsedInterval); - - System.out.print((latencyCountInterval > 0 ? - ", min/avg/max latency: " + - minLatency/1000L + "/" + - cumulativeLatencyInterval / (1000L * latencyCountInterval) + "/" + - maxLatency/1000L + " microseconds" : - "")); - - System.out.println(); - } - - private void showRate(String descr, long count, boolean display, - long elapsed) { - if (display) { - System.out.print(", " + descr + ": " + formatRate(1000.0 * count / elapsed) + " msg/s"); - } - } - - public void printFinal() { - long now = System.currentTimeMillis(); - - System.out.println("sending rate avg: " + - formatRate(sendCountTotal * 1000.0 / (now - startTime)) + - " msg/s"); - - long elapsed = now - startTime; - if (elapsed > 0) { - System.out.println("recving rate avg: " + - formatRate(recvCountTotal * 1000.0 / elapsed) + - " msg/s"); - } - } - - private static String formatRate(double rate) { - if (rate == 0.0) return String.format("%d", (long)rate); - else if (rate < 1) return String.format("%1.2f", rate); - else if (rate < 10) return String.format("%1.1f", rate); - else return String.format("%d", (long)rate); - } - } -} diff --git a/test/src/com/rabbitmq/examples/PerfTestMulti.java b/test/src/com/rabbitmq/examples/PerfTestMulti.java deleted file mode 100644 index cf6f86cdda..0000000000 --- a/test/src/com/rabbitmq/examples/PerfTestMulti.java +++ /dev/null @@ -1,102 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.examples.perf.Scenario; -import com.rabbitmq.examples.perf.ScenarioFactory; -import com.rabbitmq.tools.json.JSONReader; -import com.rabbitmq.tools.json.JSONWriter; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.io.Reader; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class PerfTestMulti { - private static final ConnectionFactory factory = new ConnectionFactory(); - - private static final Map results = new HashMap(); - - @SuppressWarnings("unchecked") - public static void main(String[] args) throws Exception { - if (args.length != 2) { - System.out.println("Usage: PerfTestMulti input-json-file output-json-file"); - System.exit(1); - } - String inJSON = args[0]; - String outJSON = args[1]; - List scenariosJSON = null; - try { - scenariosJSON = (List) new JSONReader().read(readFile(inJSON)); - } catch (FileNotFoundException e) { - System.out.println("Input json file " + inJSON + " could not be found"); - System.exit(1); - } - if (scenariosJSON == null) { - System.out.println("Input json file " + inJSON + " could not be parsed"); - System.exit(1); - } - Scenario[] scenarios = new Scenario[scenariosJSON.size()]; - for (int i = 0; i < scenariosJSON.size(); i++) { - scenarios[i] = ScenarioFactory.fromJSON(scenariosJSON.get(i), factory); - } - runStaticBrokerTests(scenarios); - writeJSON(outJSON); - } - - private static String readFile(String path) throws IOException { - final char[] buf = new char[4096]; - StringBuilder out = new StringBuilder(); - Reader in = new InputStreamReader(new FileInputStream(path), "UTF-8"); - try { - int chars; - while ((chars = in.read(buf, 0, buf.length)) > 0) { - out.append(buf, 0, chars); - } - } finally { - in.close(); - } - return out.toString(); - } - - private static void writeJSON(String outJSON) throws IOException { - FileWriter outFile = new FileWriter(outJSON); - PrintWriter out = new PrintWriter(outFile); - out.println(new JSONWriter(true).write(results)); - outFile.close(); - } - - private static void runStaticBrokerTests(Scenario[] scenarios) throws Exception { - runTests(scenarios); - } - - private static void runTests(Scenario[] scenarios) throws Exception { - for (Scenario scenario : scenarios) { - System.out.print("Running scenario '" + scenario.getName() + "' "); - scenario.run(); - System.out.println(); - results.put(scenario.getName(), scenario.getStats().results()); - } - } -} diff --git a/test/src/com/rabbitmq/examples/ProducerMain.java b/test/src/com/rabbitmq/examples/ProducerMain.java deleted file mode 100644 index a57dbe4995..0000000000 --- a/test/src/com/rabbitmq/examples/ProducerMain.java +++ /dev/null @@ -1,234 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.AMQP.BasicProperties; - -public class ProducerMain implements Runnable { - public static final int SUMMARY_EVERY_MS = 1000; - - public static final int LATENCY_MESSAGE_COUNT = 60000; - - public static final int SEND_RATE = 100000; - - public static final int OUTSTANDING_LIMIT = Integer.MAX_VALUE; - - public static final int POLL_MS = 2; - - public static int optArg(String name, String[] args, int index, int def) { - return summariseArg(name, (args.length > index) ? Integer.parseInt(args[index]) : def); - } - - public static String optArg(String name, String[] args, int index, String def) { - return summariseArg(name, (args.length > index) ? args[index] : def); - } - - public static boolean optArg(String name, String[] args, int index, boolean def) { - return summariseArg(name, (args.length > index) ? Boolean.valueOf(args[index]).booleanValue() : def); - } - - public static int summariseArg(String name, int value) { - System.out.println(name + " = " + value); - return value; - } - - public static String summariseArg(String name, String value) { - System.out.println(name + " = " + value); - return value; - } - - public static boolean summariseArg(String name, boolean value) { - System.out.println(name + " = " + value); - return value; - } - - public static void main(String[] args) { - try { - final String uri = optArg("uri", args, 0, "amqp://localhost"); - int rateLimit = optArg("rateLimit", args, 1, SEND_RATE); - int messageCount = optArg("messageCount", args, 2, LATENCY_MESSAGE_COUNT); - boolean sendCompletion = optArg("sendCompletion", args, 3, false); - int commitEvery = optArg("commitEvery", args, 4, -1); - boolean sendLatencyInfo = optArg("sendLatencyInfo", args, 5, true); - final Connection conn = new ConnectionFactory(){{setUri(uri);}}.newConnection(); - //if (commitEvery > 0) { conn.getSocket().setTcpNoDelay(true); } - System.out.println("Channel 0 fully open."); - new ProducerMain(conn, rateLimit, messageCount, sendCompletion, commitEvery, sendLatencyInfo).run(); - } catch (Exception e) { - System.err.println("Main thread caught exception: " + e); - e.printStackTrace(); - System.exit(1); - } - } - - public static void sleep(int ms) { - try { - Thread.sleep(ms); - } catch (InterruptedException ie) { - // no special processing required - } - } - - public final Connection _connection; - - public Channel _channel; - - public final int _rateLimit; - - public final int _messageCount; - - public final boolean _sendCompletion; - - public final int _commitEvery; - - public final boolean _sendLatencyInfo; - - public ProducerMain(Connection connection, int rateLimit, int messageCount, boolean sendCompletion, int commitEvery, boolean sendLatencyInfo) { - _connection = connection; - _rateLimit = rateLimit; - _messageCount = messageCount; - _sendCompletion = sendCompletion; - _commitEvery = commitEvery; - _sendLatencyInfo = sendLatencyInfo; - } - - public boolean shouldPersist() { - return _commitEvery >= 0; - } - - public boolean shouldCommit() { - return _commitEvery > 0; - } - - public void run() { - try { - runIt(); - } catch (IOException ex) { - throw new RuntimeException(ex); // wrap and re-throw - } - } - - private void runIt() throws IOException { - _channel = _connection.createChannel(); - - String queueName = "test queue"; - _channel.queueDeclare(queueName, true, false, false, null); - - if (shouldCommit()) { - _channel.txSelect(); - } - sendBatch(queueName); - - if (_sendCompletion) { - String exchangeName = "test completion"; - _channel.exchangeDeclarePassive(exchangeName); - _channel.basicPublish(exchangeName, "", MessageProperties.BASIC, new byte[0]); - if (shouldCommit()) - _channel.txCommit(); - _channel.exchangeDelete(exchangeName); - } - - _channel.close(); - System.out.println("Closing."); - _connection.close(); - System.out.println("Leaving ProducerMain.run()."); - } - - public void primeServer(String queueName) throws IOException { - System.out.println("Priming server..."); - for (int i = 0; i < 2000; i++) { - _channel.basicPublish("", queueName, MessageProperties.MINIMAL_BASIC, new byte[0]); - } - sleep(500); - System.out.println("...starting."); - } - - public void sendBatch(String queueName) throws IOException { - //primeServer(queueName); - - long startTime = System.currentTimeMillis(); - int sent = 0; - int previousSent = 0; - long previousReportTime = startTime; - - long nextSummaryTime = startTime + SUMMARY_EVERY_MS; - byte[] message = new byte[256]; - BasicProperties props = shouldPersist() ? - MessageProperties.MINIMAL_PERSISTENT_BASIC : - MessageProperties.MINIMAL_BASIC; - for (int i = 0; i < _messageCount; i++) { - ByteArrayOutputStream acc = new ByteArrayOutputStream(); - DataOutputStream d = new DataOutputStream(acc); - long now = System.currentTimeMillis(); - d.writeInt(_messageCount - i - 1); - if (_sendLatencyInfo) { - d.writeLong(now); - } else { - d.writeLong(-1); - } - d.flush(); - acc.flush(); - byte[] message0 = acc.toByteArray(); - System.arraycopy(message0, 0, message, 0, message0.length); - _channel.basicPublish("", queueName, props, message); - sent++; - if (shouldCommit()) { - if ((sent % _commitEvery) == 0) { - _channel.txCommit(); - } - } - if (now > nextSummaryTime) { - summariseProgress(startTime, now, sent, previousReportTime, previousSent); - previousSent = sent; - previousReportTime = now; - nextSummaryTime += SUMMARY_EVERY_MS; - } - while (((1000.0 * i) / (now - startTime)) > _rateLimit) { - sleep(POLL_MS); - now = System.currentTimeMillis(); - } - } - - long stopTime = System.currentTimeMillis(); - long totalDelta = stopTime - startTime; - report(totalDelta); - } - - public void report(long totalDelta) { - System.out - .println("PRODUCER - Overall: " - + String.format("%d messages in %dms, a rate of %.2f msgs/sec", _messageCount, - totalDelta, - (_messageCount / (totalDelta / 1000.0)))); - } - - public void summariseProgress(long startTime, long now, int sent, long previousReportTime, int previousSent) { - int countOverInterval = sent - previousSent; - double intervalRate = countOverInterval / ((now - previousReportTime) / 1000.0); - System.out.println((now - startTime) + " ms: Sent " + sent + " - " - + countOverInterval + " since last report (" - + (int) intervalRate + " Hz)"); - } -} diff --git a/test/src/com/rabbitmq/examples/SendString.java b/test/src/com/rabbitmq/examples/SendString.java deleted file mode 100644 index be6cde2ae8..0000000000 --- a/test/src/com/rabbitmq/examples/SendString.java +++ /dev/null @@ -1,52 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; - -public class SendString { - public static void main(String[] args) { - try { - if (args.length < 5) { - System.err.println("Usage: SendString "); - System.exit(1); - } - - String uri = args[0]; - String exchange = args[1]; - String exchangeType = args[2]; - String routingKey = args[3]; - String message = args[4]; - - ConnectionFactory cfconn = new ConnectionFactory(); - cfconn.setUri(uri); - Connection conn = cfconn.newConnection(); - Channel ch = conn.createChannel(); - - ch.exchangeDeclare(exchange, exchangeType); - ch.basicPublish(exchange, routingKey, null, message.getBytes()); - ch.close(); - conn.close(); - } catch (Exception e) { - System.err.println("Main thread caught exception: " + e); - e.printStackTrace(); - System.exit(1); - } - } -} diff --git a/test/src/com/rabbitmq/examples/SimpleConsumer.java b/test/src/com/rabbitmq/examples/SimpleConsumer.java deleted file mode 100644 index 51154f3abe..0000000000 --- a/test/src/com/rabbitmq/examples/SimpleConsumer.java +++ /dev/null @@ -1,51 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.QueueingConsumer; - -public class SimpleConsumer { - public static void main(String[] args) { - try { - String uri = (args.length > 0) ? args[0] : "amqp://localhost"; - String queueName = (args.length > 1) ? args[1] : "SimpleQueue"; - - ConnectionFactory connFactory = new ConnectionFactory(); - connFactory.setUri(uri); - Connection conn = connFactory.newConnection(); - - final Channel ch = conn.createChannel(); - - ch.queueDeclare(queueName, false, false, false, null); - - QueueingConsumer consumer = new QueueingConsumer(ch); - ch.basicConsume(queueName, consumer); - while (true) { - QueueingConsumer.Delivery delivery = consumer.nextDelivery(); - System.out.println("Message: " + new String(delivery.getBody())); - ch.basicAck(delivery.getEnvelope().getDeliveryTag(), false); - } - } catch (Exception ex) { - System.err.println("Main thread caught exception: " + ex); - ex.printStackTrace(); - System.exit(1); - } - } -} diff --git a/test/src/com/rabbitmq/examples/SimpleProducer.java b/test/src/com/rabbitmq/examples/SimpleProducer.java deleted file mode 100644 index eb85dbdbfa..0000000000 --- a/test/src/com/rabbitmq/examples/SimpleProducer.java +++ /dev/null @@ -1,50 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; - -public class SimpleProducer { - public static void main(String[] args) { - try { - String uri = (args.length > 0) ? args[0] : "amqp://localhost"; - String message = (args.length > 1) ? args[1] : - "the time is " + new java.util.Date().toString(); - String exchange = (args.length > 2) ? args[2] : ""; - String routingKey = (args.length > 3) ? args[3] : "SimpleQueue"; - - ConnectionFactory cfconn = new ConnectionFactory(); - cfconn.setUri(uri); - Connection conn = cfconn.newConnection(); - - Channel ch = conn.createChannel(); - - if (exchange.equals("")) { - ch.queueDeclare(routingKey, false, false, false, null); - } - ch.basicPublish(exchange, routingKey, null, message.getBytes()); - ch.close(); - conn.close(); - } catch (Exception e) { - System.err.println("Main thread caught exception: " + e); - e.printStackTrace(); - System.exit(1); - } - } -} diff --git a/test/src/com/rabbitmq/examples/SimpleTopicConsumer.java b/test/src/com/rabbitmq/examples/SimpleTopicConsumer.java deleted file mode 100644 index c0a8f22200..0000000000 --- a/test/src/com/rabbitmq/examples/SimpleTopicConsumer.java +++ /dev/null @@ -1,81 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.QueueingConsumer; - -public class SimpleTopicConsumer { - public static void main(String[] args) { - try { - if (args.length < 1 || args.length > 4) { - System.err.print("Usage: SimpleTopicConsumer brokeruri [topicpattern\n" + - " [exchange\n" + - " [queue]]]\n" + - "where\n" + - " - topicpattern defaults to \"#\",\n" + - " - exchange to \"amq.topic\", and\n" + - " - queue to a private, autodelete queue\n"); - System.exit(1); - } - - String uri = (args.length > 0) ? args[0] : "amqp://localhost"; - String topicPattern = (args.length > 1) ? args[1] : "#"; - String exchange = (args.length > 2) ? args[2] : null; - String queue = (args.length > 3) ? args[3] : null; - - ConnectionFactory cfconn = new ConnectionFactory(); - cfconn.setUri(uri); - Connection conn = cfconn.newConnection(); - - final Channel channel = conn.createChannel(); - - if (exchange == null) { - exchange = "amq.topic"; - } else { - channel.exchangeDeclare(exchange, "topic"); - } - - if (queue == null) { - queue = channel.queueDeclare().getQueue(); - } else { - channel.queueDeclare(queue, false, false, false, null); - } - - channel.queueBind(queue, exchange, topicPattern); - - System.out.println("Listening to exchange " + exchange + ", pattern " + topicPattern + - " from queue " + queue); - - QueueingConsumer consumer = new QueueingConsumer(channel); - channel.basicConsume(queue, consumer); - while (true) { - QueueingConsumer.Delivery delivery = consumer.nextDelivery(); - Envelope envelope = delivery.getEnvelope(); - System.out.println(envelope.getRoutingKey() + ": " + new String(delivery.getBody())); - channel.basicAck(envelope.getDeliveryTag(), false); - } - } catch (Exception ex) { - System.err.println("Main thread caught exception: " + ex); - ex.printStackTrace(); - System.exit(1); - } - } -} diff --git a/test/src/com/rabbitmq/examples/SimpleTopicProducer.java b/test/src/com/rabbitmq/examples/SimpleTopicProducer.java deleted file mode 100644 index 4257e2a91d..0000000000 --- a/test/src/com/rabbitmq/examples/SimpleTopicProducer.java +++ /dev/null @@ -1,66 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; - -public class SimpleTopicProducer { - public static final String DEFAULT_TOPIC = "one.two.three.four"; - - public static void main(String[] args) { - try { - if (args.length < 1 || args.length > 4) { - System.err.print("Usage: SimpleTopicProducer brokeruri [topic\n" + - " [exchange\n" + - " [message]]]\n" + - "where\n" + - " - topic defaults to \""+DEFAULT_TOPIC+"\",\n" + - " - exchange defaults to \"amq.topic\", and\n" + - " - message defaults to a time-of-day message\n"); - System.exit(1); - } - String uri = (args.length > 0) ? args[0] : "amqp://localhost"; - String topic = (args.length > 1) ? args[1] : DEFAULT_TOPIC; - String exchange = (args.length > 2) ? args[2] : null; - String message = (args.length > 3) ? args[3] : - "the time is " + new java.util.Date().toString(); - - ConnectionFactory cfconn = new ConnectionFactory(); - cfconn.setUri(uri); - Connection conn = cfconn.newConnection(); - - Channel ch = conn.createChannel(); - - if (exchange == null) { - exchange = "amq.topic"; - } else { - ch.exchangeDeclare(exchange, "topic"); - } - - System.out.println("Sending to exchange " + exchange + ", topic " + topic); - ch.basicPublish(exchange, topic, null, message.getBytes()); - ch.close(); - conn.close(); - } catch (Exception e) { - System.err.println("Main thread caught exception: " + e); - e.printStackTrace(); - System.exit(1); - } - } -} diff --git a/test/src/com/rabbitmq/examples/SpammyTopicProducer.java b/test/src/com/rabbitmq/examples/SpammyTopicProducer.java deleted file mode 100644 index 4e49935c0f..0000000000 --- a/test/src/com/rabbitmq/examples/SpammyTopicProducer.java +++ /dev/null @@ -1,96 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import java.util.Random; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; - -/** - * This producer creates messages with constantly changing topic keys, sending as - * fast as it can. This can be used to test the topic key cache in the broker. - */ - -public class SpammyTopicProducer { - public static final String DEFAULT_TOPIC_PREFIX = "top."; - private static final int SUMMARISE_EVERY = 1000; - - public static void main(String[] args) { - try { - if (args.length < 1 || args.length > 4) { - System.err.print("Usage: SpammyTopicProducer brokeruri [topic prefix\n" + - " [exchange\n" + - " [message]]]\n" + - "where\n" + - " - topic defaults to \""+DEFAULT_TOPIC_PREFIX+"\",\n" + - " - exchange defaults to \"amq.topic\", and\n" + - " - message defaults to a time-of-day message\n"); - System.exit(1); - } - String uri = (args.length > 0) ? args[0] : "amqp://localhost"; - String topicPrefix = (args.length > 1) ? args[1] : DEFAULT_TOPIC_PREFIX; - String exchange = (args.length > 2) ? args[2] : null; - String message = (args.length > 3) ? args[3] : - "the time is " + new java.util.Date().toString(); - - ConnectionFactory cfconn = new ConnectionFactory(); - cfconn.setUri(uri); - Connection conn = cfconn.newConnection(); - - Channel ch = conn.createChannel(); - - if (exchange == null) { - exchange = "amq.topic"; - } else { - ch.exchangeDeclare(exchange, "topic"); - } - - System.out.println("Sending to exchange " + exchange + ", prefix: " + topicPrefix); - - int thisTimeCount = 0; - int allTimeCount = 0; - long startTime = System.currentTimeMillis(); - long nextSummaryTime = startTime; - while (true) { - ch.basicPublish(exchange, topicPrefix + newSuffix(), null, message.getBytes()); - thisTimeCount++; - allTimeCount++; - long now = System.currentTimeMillis(); - if (now > nextSummaryTime) { - int thisTimeRate = (int)(1.0 * thisTimeCount / (now - nextSummaryTime + SUMMARISE_EVERY) * SUMMARISE_EVERY); - int allTimeRate = (int)(1.0 * allTimeCount / (now - startTime) * SUMMARISE_EVERY); - System.out.println(thisTimeRate + " / sec currently, " + allTimeRate + " / sec average"); - thisTimeCount = 0; - nextSummaryTime = now + SUMMARISE_EVERY; - } - } - - //ch.close(); - //conn.close(); - } catch (Exception e) { - System.err.println("Main thread caught exception: " + e); - e.printStackTrace(); - System.exit(1); - } - } - - private static String newSuffix() { - return "" + new Random().nextLong(); - } -} diff --git a/test/src/com/rabbitmq/examples/StressPersister.java b/test/src/com/rabbitmq/examples/StressPersister.java deleted file mode 100644 index 4338b9e9bf..0000000000 --- a/test/src/com/rabbitmq/examples/StressPersister.java +++ /dev/null @@ -1,221 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URISyntaxException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.GnuParser; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; - -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.MessageProperties; -import com.rabbitmq.client.QueueingConsumer; - -public class StressPersister { - public static void main(String[] args) { - try { - StressPersister sp = new StressPersister(); - sp.configure(args); - sp.run(); - System.exit(0); - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - } - - private static String strArg(CommandLine cmd, char opt, String def) { - return cmd.getOptionValue(opt, def); - } - - private static int intArg(CommandLine cmd, char opt, int def) { - return Integer.parseInt(cmd.getOptionValue(opt, Integer.toString(def))); - } - - private static int sizeArg(CommandLine cmd, char opt, int def) { - String arg = cmd.getOptionValue(opt, Integer.toString(def)); - int multiplier = 1; - boolean strip = false; - switch (Character.toLowerCase(arg.charAt(arg.length() - 1))) { - case 'b': multiplier = 1; strip = true; break; - case 'k': multiplier = 1024; strip = true; break; - case 'm': multiplier = 1048576; strip = true; break; - default: break; - } - if (strip) { - arg = arg.substring(0, arg.length() - 1); - } - return multiplier * Integer.parseInt(arg); - } - - public String uri; - - public String commentText; - public int backlogSize; - public int bodySize; - public int repeatCount; - public int sampleGranularity; - - public ConnectionFactory connectionFactory; - public long topStartTime; - public PrintWriter logOut; - - public void configure(String[] args) - throws ParseException, URISyntaxException, NoSuchAlgorithmException, KeyManagementException - { - Options options = new Options(); - options.addOption(new Option("h", "uri", true, "AMQP URI")); - options.addOption(new Option("C", "comment", true, "comment text")); - options.addOption(new Option("b", "backlog", true, "backlog size")); - options.addOption(new Option("B", "bodysize", true, "body size")); - options.addOption(new Option("c", "count", true, "plateau repeat count")); - options.addOption(new Option("s", "sampleevery", true, "sample granularity")); - CommandLineParser parser = new GnuParser(); - CommandLine cmd = parser.parse(options, args); - - uri = strArg(cmd, 'h', "amqp://localhost"); - - commentText = strArg(cmd, 'C', ""); - if ("".equals(commentText)) { - throw new IllegalArgumentException("Comment text must be nonempty"); - } - - backlogSize = intArg(cmd, 'b', 5000); - bodySize = sizeArg(cmd, 'B', 16384); - repeatCount = intArg(cmd, 'c', backlogSize * 5); - sampleGranularity = intArg(cmd, 's', Math.max(5, repeatCount / 250)); - - connectionFactory = new ConnectionFactory(); - connectionFactory.setUri(uri); - } - - public Connection newConnection() throws IOException { - return connectionFactory.newConnection(); - } - - public void run() throws IOException, InterruptedException { - topStartTime = System.currentTimeMillis(); - String logFileName = String.format("stress-persister-b%08d-B%010d-c%08d-s%06d-%s.out", - backlogSize, bodySize, repeatCount, sampleGranularity, commentText); - logOut = new PrintWriter(logFileName); - System.out.println(logFileName); - trace("Logging to " + logFileName); - publishOneInOneOutReceive(backlogSize, bodySize, repeatCount, sampleGranularity); - logOut.close(); - } - - public void trace(String message) { - long now = System.currentTimeMillis(); - long delta = now - topStartTime; - String s = String.format("# %010d ms: %s", delta, message); - System.out.println(s); - logOut.println(s); - logOut.flush(); - } - - public void redeclare(String q, Channel chan) throws IOException { - trace("Redeclaring queue " + q); - chan.queueDeclare(q, true, false, false, null); - // ^^ synchronous operation to get some kind - // of indication back from the server that it's caught up with us - } - - public void publishOneInOneOutReceive(int backlogSize, int bodySize, int repeatCount, int sampleGranularity) throws IOException, InterruptedException { - String q = "test"; - BasicProperties props = MessageProperties.MINIMAL_PERSISTENT_BASIC; - Connection conn = newConnection(); - Channel chan = conn.createChannel(); - byte[] body = new byte[bodySize]; - List plateauSampleTimes = new ArrayList(repeatCount); - List plateauSampleDeltas = new ArrayList(repeatCount); - - trace("Declaring and purging queue " + q); - chan.queueDeclare(q, true, false, false, null); - chan.queuePurge(q); - chan.basicQos(1); - - trace("Building backlog out to " + backlogSize + " messages, each " + bodySize + " bytes long"); - for (int i = 0; i < backlogSize; i++) { - chan.basicPublish("", q, props, body); - } - - redeclare(q, chan); - - trace("Beginning plateau of " + repeatCount + " repeats, sampling every " + sampleGranularity + " messages"); - - QueueingConsumer consumer = new QueueingConsumer(chan); - chan.basicConsume(q, consumer); - - long startTime = System.currentTimeMillis(); - for (int i = 0; i < repeatCount; i++) { - if (((i % sampleGranularity) == 0) && (i > 0)) { - long now = System.currentTimeMillis(); - double delta = 1000 * (now - startTime) / (double) sampleGranularity; - plateauSampleTimes.add(now); - plateauSampleDeltas.add(delta); - System.out.print(String.format("# %3d%%; %012d --> %g microseconds/roundtrip \r", - (100 * i / repeatCount), - now, - delta)); - startTime = System.currentTimeMillis(); - } - chan.basicPublish("", q, props, body); - QueueingConsumer.Delivery d = consumer.nextDelivery(); - chan.basicAck(d.getEnvelope().getDeliveryTag(), false); - } - System.out.println(); - - trace("Switching QOS to unlimited"); - chan.basicQos(0); - - trace("Draining backlog"); - for (int i = 0; i < backlogSize; i++) { - QueueingConsumer.Delivery d = consumer.nextDelivery(); - chan.basicAck(d.getEnvelope().getDeliveryTag(), false); - } - - redeclare(q, chan); - - trace("Closing connection"); - chan.close(); - conn.close(); - - trace("Sample results (timestamp in milliseconds since epoch; microseconds/roundtrip)"); - System.out.println("(See log file for results; final sample was " + - plateauSampleDeltas.get(plateauSampleDeltas.size() - 1) + ")"); - for (int i = 0; i < plateauSampleTimes.size(); i++) { - String s = String.format("%d %d", - plateauSampleTimes.get(i), - plateauSampleDeltas.get(i).longValue()); - logOut.println(s); - } - logOut.flush(); - } -} diff --git a/test/src/com/rabbitmq/examples/TestMain.java b/test/src/com/rabbitmq/examples/TestMain.java deleted file mode 100644 index 8e9d7b272c..0000000000 --- a/test/src/com/rabbitmq/examples/TestMain.java +++ /dev/null @@ -1,499 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.examples; - -import java.io.IOException; -import java.io.InputStream; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.net.URISyntaxException; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Address; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.DefaultSocketConfigurator; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.GetResponse; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.Method; -import com.rabbitmq.client.ReturnListener; -import com.rabbitmq.client.ShutdownSignalException; -import com.rabbitmq.client.SocketConfigurator; -import com.rabbitmq.client.impl.AMQConnection; -import com.rabbitmq.client.impl.FrameHandler; -import com.rabbitmq.client.impl.FrameHandlerFactory; -import com.rabbitmq.client.impl.SocketFrameHandler; -import com.rabbitmq.utility.BlockingCell; - -import javax.net.SocketFactory; - -public class TestMain { - public static void main(String[] args) throws IOException, URISyntaxException { - // Show what version this class was compiled with, to check conformance testing - Class clazz = TestMain.class; - String javaVersion = System.getProperty("java.version"); - System.out.println(clazz.getName() + " : javac v" + getCompilerVersion(clazz) + " on " + javaVersion); - try { - boolean silent = Boolean.getBoolean("silent"); - final String uri = (args.length > 0) ? args[0] : "amqp://localhost"; - runConnectionNegotiationTest(uri); - final Connection conn = new ConnectionFactory(){{setUri(uri);}}.newConnection(); - if (!silent) { - System.out.println("Channel 0 fully open."); - } - - new TestMain(conn, silent).run(); - - runProducerConsumerTest(uri, 500); - runProducerConsumerTest(uri, 0); - runProducerConsumerTest(uri, -1); - - runConnectionShutdownTests(uri); - - } catch (Exception e) { - System.err.println("Main thread caught exception: " + e); - e.printStackTrace(); - System.exit(1); - } - } - - private static class TestConnectionFactory extends ConnectionFactory { - - private final int protocolMajor; - private final int protocolMinor; - - private class TestFrameHandlerFactory extends FrameHandlerFactory { - public TestFrameHandlerFactory(int connectionTimeout, SocketFactory factory, SocketConfigurator configurator, boolean ssl) { - super(connectionTimeout, factory, configurator, ssl); - } - - @Override - public FrameHandler create(Address addr) throws IOException { - String hostName = addr.getHost(); - int portNumber = addr.getPort(); - if (portNumber == -1) portNumber = AMQP.PROTOCOL.PORT; - return new SocketFrameHandler(getSocketFactory().createSocket(hostName, portNumber)) { - @Override - public void sendHeader() throws IOException { - sendHeader(protocolMajor, protocolMinor); - } - }; - } - } - - public TestConnectionFactory(int major, int minor, String uri) - throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException - { - this.protocolMajor = major; - this.protocolMinor = minor; - setUri(uri); - } - - @Override - public FrameHandlerFactory createFrameHandlerFactory() - throws IOException { - return new TestFrameHandlerFactory(10, SocketFactory.getDefault(), new DefaultSocketConfigurator(), false); - } - } - - public static void runConnectionNegotiationTest(final String uri) - throws IOException, URISyntaxException, NoSuchAlgorithmException, KeyManagementException - { - - Connection conn; - - try { - conn = new TestConnectionFactory(0, 1, uri).newConnection(); - conn.close(); - throw new RuntimeException("expected socket close"); - } catch (IOException e) {} - - ConnectionFactory factory; - factory = new ConnectionFactory(); - factory.setUsername("invalid"); - factory.setPassword("invalid"); - try { - factory.setUri(uri); - conn = factory.newConnection(); - conn.close(); - throw new RuntimeException("expected socket close"); - } catch (IOException e) {} - - factory = new ConnectionFactory(); - factory.setRequestedChannelMax(10); - factory.setRequestedFrameMax(8192); - factory.setRequestedHeartbeat(1); - factory.setUri(uri); - conn = factory.newConnection(); - checkNegotiatedMaxValue("channel-max", 10, conn.getChannelMax()); - checkNegotiatedMaxValue("frame-max", 8192, conn.getFrameMax()); - checkNegotiatedMaxValue("heartbeat", 1, conn.getHeartbeat()); - conn.close(); - - factory = new ConnectionFactory(); - factory.setRequestedChannelMax(0); - factory.setRequestedFrameMax(0); - factory.setRequestedHeartbeat(0); - factory.setUri(uri); - conn = factory.newConnection(); - checkNegotiatedMaxValue("channel-max", 0, conn.getChannelMax()); - checkNegotiatedMaxValue("frame-max", 0, conn.getFrameMax()); - checkNegotiatedMaxValue("heartbeat", 0, conn.getHeartbeat()); - conn.close(); - - conn = new ConnectionFactory(){{setUri(uri);}}.newConnection(); - conn.close(); - } - - private static void checkNegotiatedMaxValue(String name, - int requested, - int negotiated) { - if (requested != 0 && (negotiated == 0 || negotiated > requested)) { - throw new RuntimeException("requested " + name + " of " + - requested + ", negotiated " + - negotiated); - } - } - - public static void runConnectionShutdownTests(final String uri) - throws IOException, URISyntaxException, NoSuchAlgorithmException, KeyManagementException - { - Connection conn; - Channel ch; - // Test what happens when a connection is shut down w/o first - // closing the channels. - conn = new ConnectionFactory(){{setUri(uri);}}.newConnection(); - ch = conn.createChannel(); - conn.close(); - // Test what happens when we provoke an error - conn = new ConnectionFactory(){{setUri(uri);}}.newConnection(); - ch = conn.createChannel(); - try { - ch.exchangeDeclare("mumble", "invalid"); - throw new RuntimeException("expected shutdown"); - } catch (IOException e) { - } - // Test what happens when we just kill the connection - conn = new ConnectionFactory(){{setUri(uri);}}.newConnection(); - ch = conn.createChannel(); - ((AMQConnection)conn).getFrameHandler().close(); - } - - public static void runProducerConsumerTest(String uri, int commitEvery) - throws IOException, URISyntaxException, NoSuchAlgorithmException, KeyManagementException - { - ConnectionFactory cfconnp = new ConnectionFactory(); - cfconnp.setUri(uri); - Connection connp = cfconnp.newConnection(); - ProducerMain p = new ProducerMain(connp, 2000, 10000, false, commitEvery, true); - new Thread(p).start(); - ConnectionFactory cfconnc = new ConnectionFactory(); - cfconnc.setUri(uri); - Connection connc = cfconnc.newConnection(); - ConsumerMain c = new ConsumerMain(connc, false, true); - c.run(); - } - - public static void sleep(int ms) { - try { - Thread.sleep(ms); - } catch (InterruptedException _e) { } // ignore - } - - private final Connection _connection; - - private Channel _ch1; - - private int _messageId = 0; - - private final boolean _silent; - - private volatile BlockingCell returnCell; - - public TestMain(Connection connection, boolean silent) { - _connection = connection; - _silent = silent; - } - - public Channel createChannel() throws IOException { - return _connection.createChannel(); - } - - public void log(String s) { - if (!_silent) - System.out.println(s); - } - - public void run() throws IOException { - final int batchSize = 5; - - _ch1 = createChannel(); - - String queueName =_ch1.queueDeclare().getQueue(); - - sendLotsOfTrivialMessages(batchSize, queueName); - expect(batchSize, drain(batchSize, queueName, false)); - - BlockingCell k1 = new BlockingCell(); - BlockingCell k2 = new BlockingCell(); - String cTag1 = _ch1.basicConsume(queueName, true, new BatchedTracingConsumer(true, k1, batchSize, _ch1)); - String cTag2 = _ch1.basicConsume(queueName, false, new BatchedTracingConsumer(false, k2, batchSize, _ch1)); - sendLotsOfTrivialMessages(batchSize, queueName); - sendLotsOfTrivialMessages(batchSize, queueName); - - k1.uninterruptibleGet(); - k2.uninterruptibleGet(); - _ch1.basicCancel(cTag1); - _ch1.basicCancel(cTag2); - - tryTopics(); - - queueName =_ch1.queueDeclare().getQueue(); - sendLotsOfTrivialMessages(batchSize, queueName); - expect(batchSize, drain(batchSize, queueName, true)); - - _ch1.close(); - - log("Closing."); - try { - _connection.close(); - } catch (IllegalStateException e) { - // work around bug 15794 - } - log("Leaving TestMain.run()."); - } - - private void setChannelReturnListener() { - log("Setting return listener.."); - _ch1.addReturnListener(new ReturnListener() { - public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) - throws IOException { - Method method = new AMQP.Basic.Return.Builder() - .replyCode(replyCode) - .replyText(replyText) - .exchange(exchange) - .routingKey(routingKey) - .build(); - log("Handling return with body " + new String(body)); - TestMain.this.returnCell.set(new Object[] { method, properties, body }); - } - }); - } - - public class UnexpectedSuccessException extends IOException { - /** - * Default version UID for serializable class - */ - private static final long serialVersionUID = 1L; - - public UnexpectedSuccessException() { - // no special handling needed for default constructor - } - } - - public class TracingConsumer extends DefaultConsumer { - public TracingConsumer(Channel ch) { - super(ch); - } - - @Override public void handleConsumeOk(String c) { - log(this + ".handleConsumeOk(" + c + ")"); - super.handleConsumeOk(c); - } - - @Override public void handleCancelOk(String c) { - log(this + ".handleCancelOk(" + c + ")"); - super.handleCancelOk(c); - } - - @Override public void handleShutdownSignal(String c, ShutdownSignalException sig) { - log(this + ".handleShutdownSignal(" + c + ", " + sig + ")"); - super.handleShutdownSignal(c, sig); - } - } - - public class BatchedTracingConsumer extends TracingConsumer { - final boolean _autoAck; - - final BlockingCell _k; - - final int _batchSize; - - int _counter; - - public BatchedTracingConsumer(boolean autoAck, BlockingCell k, int batchSize, Channel ch) { - super(ch); - _autoAck = autoAck; - _k = k; - _batchSize = batchSize; - _counter = 0; - } - - @Override public void handleDelivery(String consumer_Tag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { - log("Async message (" + _counter + "," + (_autoAck ? "autoack" : "ack") + "): " + new String(body)); - _counter++; - if (_counter == _batchSize) { - if (!_autoAck) { - log("Acking batch."); - getChannel().basicAck(envelope.getDeliveryTag(), true); - } - _k.set(new Object()); - } - } - } - - public void sendLotsOfTrivialMessages(int batchSize, String routingKey) throws IOException { - for (int i = 0; i < batchSize; i++) { - String messageText = "(" + _messageId + ") On the third tone, the time will be " + new java.util.Date(); - _messageId++; - publish1("", routingKey, messageText); - // sleep(200); - } - } - - public void expect(int expected, int actual) { - if (expected != actual) { - throw new AssertionError("Expected " + expected + ", but actually got " + actual); - } - } - - public void assertNull(Object o) { - if (o != null) { - throw new AssertionError("Expected null object, got " + o); - } - } - - public void assertNonNull(Object o) { - if (o == null) { - throw new AssertionError("Expected non-null object"); - } - } - - public int drain(int batchSize, String queueName, boolean autoAck) throws IOException { - long latestTag = 0; - boolean notEmpty = true; - int remaining = batchSize; - int count = 0; - - while (notEmpty && (remaining > 0)) { - for (int i = 0; (i < 2) && (remaining > 0); i++) { - GetResponse c = _ch1.basicGet(queueName, autoAck); - if (c == null) { - notEmpty = false; - } else { - String msg = new String(c.getBody()); - log("Got message (" + c.getMessageCount() + " left in q): " + msg); - latestTag = c.getEnvelope().getDeliveryTag(); - remaining--; - count++; - } - } - if (!autoAck && latestTag != 0) { - _ch1.basicAck(latestTag, true); - latestTag = 0; - } - } - log("Drained, remaining in batch = " + remaining + "."); - return count; - } - - public void publish1(String x, String routingKey, String body) throws IOException { - _ch1.basicPublish(x, routingKey, MessageProperties.TEXT_PLAIN, body.getBytes()); - } - - public void publish2(String x, String routingKey, String body) throws IOException { - _ch1.basicPublish(x, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, body.getBytes()); - } - - public void tryTopics() throws IOException { - String q1 = "tryTopicsQueue1"; - String q2 = "tryTopicsQueue2"; - String q3 = "tryTopicsQueue3"; - String x = "tryTopicsExch"; - _ch1.queueDeclare(q1, false, true, true, null); - _ch1.queueDeclare(q2, false, true, true, null); - _ch1.queueDeclare(q3, false, true, true, null); - _ch1.exchangeDeclare(x, "topic", false, true, null); - _ch1.queueBind(q1, x, "test.#"); - _ch1.queueBind(q2, x, "test.test"); - _ch1.queueBind(q3, x, "*.test.#"); - - log("About to publish to topic queues"); - publish1(x, "", "A"); // no binding matches - publish1(x, "test", "B"); // matches q1 - publish1(x, "test.test", "C"); // matches q1, q2, q3 - publish1(x, "test.test.test", "D"); // matches q1, q3 - - log("About to drain q1"); - expect(3, drain(10, q1, true)); - log("About to drain q2"); - expect(1, drain(10, q2, true)); - log("About to drain q3"); - expect(2, drain(10, q3, true)); - } - - public void doBasicReturn(BlockingCell cell, int expectedCode) { - Object[] a = (Object[]) cell.uninterruptibleGet(); - AMQP.Basic.Return method = (AMQP.Basic.Return) a[0]; - log("Returned: " + method); - log(" - props: " + a[1]); - log(" - body: " + new String((byte[]) a[2])); - int replyCode = method.getReplyCode(); - if (replyCode != expectedCode) { - System.err.println("Eek! Got basic return with code " + replyCode + ", but expected code " + expectedCode); - System.exit(1); - } - } - - private void unsetChannelReturnListener() { - _ch1.clearReturnListeners(); - log("ReturnListeners unset"); - } - - public void waitForKey(String prompt) throws IOException { - if (!_silent) { - System.out.println(prompt); - System.out.println("[Press return to continue]"); - while (System.in.read() != 10) { - // do nothing - } - } - } - - // utility: tell what Java compiler version a class was compiled with - public static String getCompilerVersion(Class clazz) throws IOException { - String resourceName = "/" + clazz.getName().replace('.', '/') + ".class"; - System.out.println(resourceName); - - // get binary class data - InputStream in = clazz.getResourceAsStream(resourceName); - - // skip over the magic number (todo: make sure it's 0xCAFEBABE - check endedness) - if (in.skip(4) != 4) { - throw new IOException("found incorrect magic number in class file"); - } - int minor = (in.read() << 8) + in.read(); - int major = (in.read() << 8) + in.read(); - in.close(); - - return major + "." + minor; - } -} diff --git a/test/src/com/rabbitmq/examples/TracerConcurrencyTest.java b/test/src/com/rabbitmq/examples/TracerConcurrencyTest.java deleted file mode 100644 index 64df3c1829..0000000000 --- a/test/src/com/rabbitmq/examples/TracerConcurrencyTest.java +++ /dev/null @@ -1,99 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; - -/** - * Java Application. Repeatedly generate (and get) messages on multiple concurrently processing channels. - *

- * This application connects to localhost port 5673, and is useful for testing {@link com.rabbitmq.tools.Tracer Tracer}. - * @see com.rabbitmq.tools.Tracer Tracer - */ -public class TracerConcurrencyTest { - - private static final String uri = "amqp://localhost:5673"; - private static final int THREADCOUNT = 3; - private static final String EXCHANGE = "tracer-exchange"; - private static final String QUEUE = "tracer-queue"; - private static final String ROUTING_KEY = ""; - - /** - * @param args command-line parameters -- all ignored. - * @throws Exception test - */ - public static void main(String[] args) throws Exception { - - final Object outputSync = new Object(); - - final Connection conn = createConnectionAndResources(); - - for (int i = 0; i < THREADCOUNT; i++) { - new TestThread(conn, outputSync).start(); - } - } - - private static class TestThread extends Thread { - private final Connection conn; - private final Object outputSync; - - private TestThread(Connection conn, Object outputSync) { - this.conn = conn; - this.outputSync = outputSync; - } - - @Override - public void run() { - try { - while (true) { - Channel ch = conn.createChannel(); - ch.basicPublish(EXCHANGE, ROUTING_KEY, null, new byte[1024 * 1024]); - ch.basicGet(QUEUE, true); - ch.close(); - } - } catch (Exception e) { - synchronized (outputSync) { - e.printStackTrace(); - System.err.println(); - } - System.exit(1); - } - } - - } - - /** - * Create connection and declare exchange and queue for local use. - * - * @return connection - */ - private static Connection createConnectionAndResources() throws Exception { - ConnectionFactory cf = new ConnectionFactory(); - cf.setUri(uri); - Connection conn = cf.newConnection(); - Channel setup = conn.createChannel(); - - setup.exchangeDeclare(EXCHANGE, "direct"); - setup.queueDeclare(QUEUE, false, false, false, null); - setup.queueBind(QUEUE, EXCHANGE, ROUTING_KEY); - - setup.close(); - return conn; - } -} diff --git a/test/src/com/rabbitmq/examples/perf/Broker.java b/test/src/com/rabbitmq/examples/perf/Broker.java deleted file mode 100644 index c4d77e4d24..0000000000 --- a/test/src/com/rabbitmq/examples/perf/Broker.java +++ /dev/null @@ -1,113 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import com.rabbitmq.tools.Host; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintWriter; - -public class Broker { - private static final String BASE = "/tmp/rabbitmq-performance/"; - private static final String SCRIPTS = "../rabbitmq-server/scripts/"; - - private static final String HIPE_C = "{rabbit, [{hipe_compile, true}]}"; - private static final String COARSE_C = "{rabbitmq_management_agent, [{force_fine_statistics, false}]}"; - - private final String name; - private final String config; - - public Broker(String name) { - this(name, "[]."); - } - - public Broker(String name, String config) { - this.name = name; - this.config = config; - } - - public void start() throws IOException { - Process pr = null; - try { - writeConfig(); - - System.out.println("Starting broker '" + name + "'..."); - ProcessBuilder pb = new ProcessBuilder(SCRIPTS + "rabbitmq-server"); - pb.environment().put("RABBITMQ_PID_FILE", pidfile()); - pb.environment().put("RABBITMQ_LOG_BASE", BASE + "logs"); - pb.environment().put("RABBITMQ_MNESIA_DIR", BASE + "db"); - pb.environment().put("RABBITMQ_PLUGINS_EXPAND_DIR", BASE + "plugins-expand"); - pb.environment().put("RABBITMQ_CONFIG_FILE", BASE + "rabbitmq"); - - pr = pb.start(); - - Host.executeCommand(SCRIPTS + "rabbitmqctl wait " + pidfile()); - - } catch (IOException e) { - System.out.println("Broker start failed!"); - assert pr != null; - String stdout = capture(pr.getInputStream()); - String stderr = capture(pr.getErrorStream()); - System.out.println(stdout); - System.out.println(stderr); - throw new RuntimeException(e); - } - } - - private String pidfile() { - return BASE + "pid"; - } - - private void writeConfig() throws IOException { - new File(BASE).mkdirs(); - FileWriter outFile = new FileWriter(BASE + "rabbitmq.config"); - PrintWriter out = new PrintWriter(outFile); - out.println(config); - outFile.close(); - } - - public void stop() { - System.out.println("Stopping broker '" + name + "' ..."); - try { - Host.executeCommand(SCRIPTS + "rabbitmqctl stop " + pidfile()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public String getName() { - return name; - } - - - private static String capture(InputStream is) - throws IOException - { - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - String line; - StringBuilder buff = new StringBuilder(); - while ((line = br.readLine()) != null) { - buff.append(line); - } - return buff.toString(); - } -} diff --git a/test/src/com/rabbitmq/examples/perf/BrokerValue.java b/test/src/com/rabbitmq/examples/perf/BrokerValue.java deleted file mode 100644 index 53b9fbe402..0000000000 --- a/test/src/com/rabbitmq/examples/perf/BrokerValue.java +++ /dev/null @@ -1,43 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import java.io.IOException; - -public class BrokerValue implements VariableValue { - private final Broker broker; - - public BrokerValue(Broker broker) { - this.broker = broker; - } - - public void setup(MulticastParams params) throws IOException { - broker.start(); - } - - public void teardown(MulticastParams params) { - broker.stop(); - } - - public String getName() { - return "broker_type"; - } - - public String getValue() { - return broker.getName(); - } -} diff --git a/test/src/com/rabbitmq/examples/perf/BrokerVariable.java b/test/src/com/rabbitmq/examples/perf/BrokerVariable.java deleted file mode 100644 index b4b7566fdd..0000000000 --- a/test/src/com/rabbitmq/examples/perf/BrokerVariable.java +++ /dev/null @@ -1,36 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import java.util.ArrayList; -import java.util.List; - -public class BrokerVariable implements Variable { - private final Broker[] brokers; - - public BrokerVariable(Broker... brokers) { - this.brokers = brokers; - } - - public List getValues() { - List values = new ArrayList(); - for (Broker b : brokers) { - values.add(new BrokerValue(b)); - } - return values; - } -} diff --git a/test/src/com/rabbitmq/examples/perf/Consumer.java b/test/src/com/rabbitmq/examples/perf/Consumer.java deleted file mode 100644 index 6ef9327814..0000000000 --- a/test/src/com/rabbitmq/examples/perf/Consumer.java +++ /dev/null @@ -1,135 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import com.rabbitmq.client.AMQP.BasicProperties; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.Envelope; -import com.rabbitmq.client.ShutdownSignalException; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -public class Consumer extends ProducerConsumerBase implements Runnable { - - private ConsumerImpl q; - private final Channel channel; - private final String id; - private final String queueName; - private final int txSize; - private final boolean autoAck; - private final int multiAckEvery; - private final Stats stats; - private final int msgLimit; - private final long timeLimit; - private final CountDownLatch latch = new CountDownLatch(1); - - public Consumer(Channel channel, String id, - String queueName, int txSize, boolean autoAck, - int multiAckEvery, Stats stats, float rateLimit, int msgLimit, int timeLimit) { - - this.channel = channel; - this.id = id; - this.queueName = queueName; - this.rateLimit = rateLimit; - this.txSize = txSize; - this.autoAck = autoAck; - this.multiAckEvery = multiAckEvery; - this.stats = stats; - this.msgLimit = msgLimit; - this.timeLimit = 1000L * timeLimit; - } - - public void run() { - try { - q = new ConsumerImpl(channel); - channel.basicConsume(queueName, autoAck, q); - if (timeLimit == 0) { - latch.await(); - } - else { - latch.await(timeLimit, TimeUnit.MILLISECONDS); - } - - } catch (IOException e) { - throw new RuntimeException(e); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ShutdownSignalException e) { - throw new RuntimeException(e); - } - } - - private class ConsumerImpl extends DefaultConsumer { - long now; - int totalMsgCount = 0; - - private ConsumerImpl(Channel channel) { - super(channel); - lastStatsTime = now = System.currentTimeMillis(); - msgCount = 0; - } - - @Override - public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { - totalMsgCount++; - msgCount++; - - if (msgLimit == 0 || msgCount <= msgLimit) { - DataInputStream d = new DataInputStream(new ByteArrayInputStream(body)); - d.readInt(); - long msgNano = d.readLong(); - long nano = System.nanoTime(); - - if (!autoAck) { - if (multiAckEvery == 0) { - channel.basicAck(envelope.getDeliveryTag(), false); - } else if (totalMsgCount % multiAckEvery == 0) { - channel.basicAck(envelope.getDeliveryTag(), true); - } - } - - if (txSize != 0 && totalMsgCount % txSize == 0) { - channel.txCommit(); - } - - now = System.currentTimeMillis(); - - stats.handleRecv(id.equals(envelope.getRoutingKey()) ? (nano - msgNano) : 0L); - delay(now); - } - if (msgLimit != 0 && msgCount >= msgLimit) { // NB: not quite the inverse of above - latch.countDown(); - } - } - - @Override - public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { - latch.countDown(); - } - - @Override - public void handleCancel(String consumerTag) throws IOException { - System.out.println("Consumer cancelled by broker. Re-consuming."); - channel.basicConsume(queueName, autoAck, q); - } - } -} diff --git a/test/src/com/rabbitmq/examples/perf/MulticastParams.java b/test/src/com/rabbitmq/examples/perf/MulticastParams.java deleted file mode 100644 index ce87580953..0000000000 --- a/test/src/com/rabbitmq/examples/perf/MulticastParams.java +++ /dev/null @@ -1,262 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ShutdownSignalException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class MulticastParams { - private long confirm = -1; - private int consumerCount = 1; - private int producerCount = 1; - private int consumerTxSize = 0; - private int producerTxSize = 0; - private int channelPrefetch = 0; - private int consumerPrefetch = 0; - private int minMsgSize = 0; - - private int timeLimit = 0; - private float producerRateLimit = 0; - private float consumerRateLimit = 0; - private int producerMsgCount = 0; - private int consumerMsgCount = 0; - - private String exchangeName = "direct"; - private String exchangeType = "direct"; - private String queueName = ""; - private String routingKey = null; - private boolean randomRoutingKey = false; - - private List flags = new ArrayList(); - - private int multiAckEvery = 0; - private boolean autoAck = true; - private boolean autoDelete = false; - - private boolean predeclared; - - public void setExchangeType(String exchangeType) { - this.exchangeType = exchangeType; - } - - public void setExchangeName(String exchangeName) { - this.exchangeName = exchangeName; - } - - public void setQueueName(String queueName) { - this.queueName = queueName; - } - - public void setRoutingKey(String routingKey) { - this.routingKey = routingKey; - } - - public void setRandomRoutingKey(boolean randomRoutingKey) { - this.randomRoutingKey = randomRoutingKey; - } - - public void setProducerRateLimit(float producerRateLimit) { - this.producerRateLimit = producerRateLimit; - } - - public void setProducerCount(int producerCount) { - this.producerCount = producerCount; - } - - public void setConsumerRateLimit(float consumerRateLimit) { - this.consumerRateLimit = consumerRateLimit; - } - - public void setConsumerCount(int consumerCount) { - this.consumerCount = consumerCount; - } - - public void setProducerTxSize(int producerTxSize) { - this.producerTxSize = producerTxSize; - } - - public void setConsumerTxSize(int consumerTxSize) { - this.consumerTxSize = consumerTxSize; - } - - public void setConfirm(long confirm) { - this.confirm = confirm; - } - - public void setAutoAck(boolean autoAck) { - this.autoAck = autoAck; - } - - public void setMultiAckEvery(int multiAckEvery) { - this.multiAckEvery = multiAckEvery; - } - - public void setChannelPrefetch(int channelPrefetch) { - this.channelPrefetch = channelPrefetch; - } - - public void setConsumerPrefetch(int consumerPrefetch) { - this.consumerPrefetch = consumerPrefetch; - } - - public void setMinMsgSize(int minMsgSize) { - this.minMsgSize = minMsgSize; - } - - public void setTimeLimit(int timeLimit) { - this.timeLimit = timeLimit; - } - - public void setProducerMsgCount(int producerMsgCount) { - this.producerMsgCount = producerMsgCount; - } - - public void setConsumerMsgCount(int consumerMsgCount) { - this.consumerMsgCount = consumerMsgCount; - } - - public void setMsgCount(int msgCount) { - setProducerMsgCount(msgCount); - setConsumerMsgCount(msgCount); - } - - public void setFlags(List flags) { - this.flags = flags; - } - - public void setAutoDelete(boolean autoDelete) { - this.autoDelete = autoDelete; - } - - public void setPredeclared(boolean predeclared) { - this.predeclared = predeclared; - } - - public int getConsumerCount() { - return consumerCount; - } - - public int getProducerCount() { - return producerCount; - } - - public int getMinMsgSize() { - return minMsgSize; - } - - public String getRoutingKey() { - return routingKey; - } - - public boolean getRandomRoutingKey() { - return randomRoutingKey; - } - - public Producer createProducer(Connection connection, Stats stats, String id) throws IOException { - Channel channel = connection.createChannel(); - if (producerTxSize > 0) channel.txSelect(); - if (confirm >= 0) channel.confirmSelect(); - if (!predeclared || !exchangeExists(connection, exchangeName)) { - channel.exchangeDeclare(exchangeName, exchangeType); - } - final Producer producer = new Producer(channel, exchangeName, id, - randomRoutingKey, flags, producerTxSize, - producerRateLimit, producerMsgCount, - minMsgSize, timeLimit, - confirm, stats); - channel.addReturnListener(producer); - channel.addConfirmListener(producer); - return producer; - } - - public Consumer createConsumer(Connection connection, Stats stats, String id) throws IOException { - Channel channel = connection.createChannel(); - if (consumerTxSize > 0) channel.txSelect(); - String qName = configureQueue(connection, id); - if (consumerPrefetch > 0) channel.basicQos(consumerPrefetch); - if (channelPrefetch > 0) channel.basicQos(channelPrefetch, true); - return new Consumer(channel, id, qName, - consumerTxSize, autoAck, multiAckEvery, - stats, consumerRateLimit, consumerMsgCount, timeLimit); - } - - public boolean shouldConfigureQueue() { - return consumerCount == 0 && !queueName.equals(""); - } - - public String configureQueue(Connection connection, String id) throws IOException { - Channel channel = connection.createChannel(); - if (!predeclared || !exchangeExists(connection, exchangeName)) { - channel.exchangeDeclare(exchangeName, exchangeType); - } - String qName = queueName; - if (!predeclared || !queueExists(connection, queueName)) { - qName = channel.queueDeclare(queueName, - flags.contains("persistent"), - false, autoDelete, - null).getQueue(); - } - channel.queueBind(qName, exchangeName, id); - channel.close(); - return qName; - } - - private static boolean exchangeExists(Connection connection, final String exchangeName) throws IOException { - return exists(connection, new Checker() { - public void check(Channel ch) throws IOException { - ch.exchangeDeclarePassive(exchangeName); - } - }); - } - - private static boolean queueExists(Connection connection, final String queueName) throws IOException { - return queueName != null && exists(connection, new Checker() { - public void check(Channel ch) throws IOException { - ch.queueDeclarePassive(queueName); - } - }); - } - - private static interface Checker { - public void check(Channel ch) throws IOException; - } - - private static boolean exists(Connection connection, Checker checker) throws IOException { - try { - Channel ch = connection.createChannel(); - checker.check(ch); - ch.close(); - return true; - } - catch (IOException e) { - ShutdownSignalException sse = (ShutdownSignalException) e.getCause(); - if (!sse.isHardError()) { - AMQP.Channel.Close closeMethod = (AMQP.Channel.Close) sse.getReason(); - if (closeMethod.getReplyCode() == AMQP.NOT_FOUND) { - return false; - } - } - throw e; - } - } -} diff --git a/test/src/com/rabbitmq/examples/perf/MulticastSet.java b/test/src/com/rabbitmq/examples/perf/MulticastSet.java deleted file mode 100644 index 13a8fd2d15..0000000000 --- a/test/src/com/rabbitmq/examples/perf/MulticastSet.java +++ /dev/null @@ -1,97 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; - -import java.io.IOException; -import java.util.UUID; - -public class MulticastSet { - private final String id; - private final Stats stats; - private final ConnectionFactory factory; - private final MulticastParams params; - - public MulticastSet(Stats stats, ConnectionFactory factory, - MulticastParams params) { - if (params.getRoutingKey() == null) { - this.id = UUID.randomUUID().toString(); - } else { - this.id = params.getRoutingKey(); - } - this.stats = stats; - this.factory = factory; - this.params = params; - } - - public void run() throws IOException, InterruptedException { - run(false); - } - - public void run(boolean announceStartup) throws IOException, InterruptedException { - Thread[] consumerThreads = new Thread[params.getConsumerCount()]; - Connection[] consumerConnections = new Connection[consumerThreads.length]; - for (int i = 0; i < consumerConnections.length; i++) { - if (announceStartup) { - System.out.println("starting consumer #" + i); - } - Connection conn = factory.newConnection(); - consumerConnections[i] = conn; - Thread t = new Thread(params.createConsumer(conn, stats, id)); - consumerThreads[i] = t; - } - - if (params.shouldConfigureQueue()) { - Connection conn = factory.newConnection(); - params.configureQueue(conn, id); - conn.close(); - } - - Thread[] producerThreads = new Thread[params.getProducerCount()]; - Connection[] producerConnections = new Connection[producerThreads.length]; - for (int i = 0; i < producerThreads.length; i++) { - if (announceStartup) { - System.out.println("starting producer #" + i); - } - Connection conn = factory.newConnection(); - producerConnections[i] = conn; - Thread t = new Thread(params.createProducer(conn, stats, id)); - producerThreads[i] = t; - } - - for (Thread consumerThread : consumerThreads) { - consumerThread.start(); - } - - for (Thread producerThread : producerThreads) { - producerThread.start(); - } - - for (int i = 0; i < producerThreads.length; i++) { - producerThreads[i].join(); - producerConnections[i].close(); - } - - for (int i = 0; i < consumerThreads.length; i++) { - consumerThreads[i].join(); - consumerConnections[i].close(); - } - } -} diff --git a/test/src/com/rabbitmq/examples/perf/MulticastValue.java b/test/src/com/rabbitmq/examples/perf/MulticastValue.java deleted file mode 100644 index 001f158abd..0000000000 --- a/test/src/com/rabbitmq/examples/perf/MulticastValue.java +++ /dev/null @@ -1,42 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -class MulticastValue implements VariableValue { - private final String name; - private final Object value; - - MulticastValue(String name, Object value) { - this.name = name; - this.value = value; - } - - public void setup(MulticastParams params) { - PerfUtil.setValue(params, name, value); - } - - public void teardown(MulticastParams params) { - } - - public String getName() { - return name; - } - - public Object getValue() { - return value; - } -} diff --git a/test/src/com/rabbitmq/examples/perf/MulticastVariable.java b/test/src/com/rabbitmq/examples/perf/MulticastVariable.java deleted file mode 100644 index 5308c34abe..0000000000 --- a/test/src/com/rabbitmq/examples/perf/MulticastVariable.java +++ /dev/null @@ -1,34 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import java.util.ArrayList; -import java.util.List; - -public class MulticastVariable implements Variable { - private final List values = new ArrayList(); - - public MulticastVariable(String name, Object... values) { - for (Object v : values) { - this.values.add(new MulticastValue(name, v)); - } - } - - public List getValues() { - return values; - } -} diff --git a/test/src/com/rabbitmq/examples/perf/PerfUtil.java b/test/src/com/rabbitmq/examples/perf/PerfUtil.java deleted file mode 100644 index 13bdd900d8..0000000000 --- a/test/src/com/rabbitmq/examples/perf/PerfUtil.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.rabbitmq.examples.perf; - -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.InvocationTargetException; - -public class PerfUtil { - public static void setValue(Object obj, Object name, Object value) { - try { - PropertyDescriptor[] props = Introspector.getBeanInfo(obj.getClass()).getPropertyDescriptors(); - for (PropertyDescriptor prop : props) { - if (prop.getName().equals(name)) { - prop.getWriteMethod().invoke(obj, value); - return; - } - } - throw new RuntimeException("Could not find property " + name + " in " + obj.getClass()); - } catch (IntrospectionException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } -} diff --git a/test/src/com/rabbitmq/examples/perf/Producer.java b/test/src/com/rabbitmq/examples/perf/Producer.java deleted file mode 100644 index 628c23321b..0000000000 --- a/test/src/com/rabbitmq/examples/perf/Producer.java +++ /dev/null @@ -1,187 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.ConfirmListener; -import com.rabbitmq.client.MessageProperties; -import com.rabbitmq.client.ReturnListener; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.UUID; -import java.util.concurrent.Semaphore; - -public class Producer extends ProducerConsumerBase implements Runnable, ReturnListener, - ConfirmListener -{ - private final Channel channel; - private final String exchangeName; - private final String id; - private final boolean randomRoutingKey; - private final boolean mandatory; - private final boolean immediate; - private final boolean persistent; - private final int txSize; - private final int msgLimit; - private final long timeLimit; - - private final Stats stats; - - private final byte[] message; - - private Semaphore confirmPool; - private final SortedSet unconfirmedSet = - Collections.synchronizedSortedSet(new TreeSet()); - - public Producer(Channel channel, String exchangeName, String id, boolean randomRoutingKey, - List flags, int txSize, - float rateLimit, int msgLimit, int minMsgSize, int timeLimit, - long confirm, Stats stats) - throws IOException { - - this.channel = channel; - this.exchangeName = exchangeName; - this.id = id; - this.randomRoutingKey = randomRoutingKey; - this.mandatory = flags.contains("mandatory"); - this.immediate = flags.contains("immediate"); - this.persistent = flags.contains("persistent"); - this.txSize = txSize; - this.rateLimit = rateLimit; - this.msgLimit = msgLimit; - this.timeLimit = 1000L * timeLimit; - this.message = new byte[minMsgSize]; - if (confirm > 0) { - this.confirmPool = new Semaphore((int)confirm); - } - this.stats = stats; - } - - public void handleReturn(int replyCode, - String replyText, - String exchange, - String routingKey, - AMQP.BasicProperties properties, - byte[] body) - throws IOException { - stats.handleReturn(); - } - - public void handleAck(long seqNo, boolean multiple) { - handleAckNack(seqNo, multiple, false); - } - - public void handleNack(long seqNo, boolean multiple) { - handleAckNack(seqNo, multiple, true); - } - - private void handleAckNack(long seqNo, boolean multiple, - boolean nack) { - int numConfirms = 0; - if (multiple) { - SortedSet confirmed = unconfirmedSet.headSet(seqNo + 1); - numConfirms += confirmed.size(); - confirmed.clear(); - } else { - unconfirmedSet.remove(seqNo); - numConfirms = 1; - } - if (nack) { - stats.handleNack(numConfirms); - } else { - stats.handleConfirm(numConfirms); - } - - if (confirmPool != null) { - for (int i = 0; i < numConfirms; ++i) { - confirmPool.release(); - } - } - - } - - public void run() { - long now; - long startTime; - startTime = now = System.currentTimeMillis(); - lastStatsTime = startTime; - msgCount = 0; - int totalMsgCount = 0; - - try { - - while ((timeLimit == 0 || now < startTime + timeLimit) && - (msgLimit == 0 || msgCount < msgLimit)) { - delay(now); - if (confirmPool != null) { - confirmPool.acquire(); - } - publish(createMessage(totalMsgCount)); - totalMsgCount++; - msgCount++; - - if (txSize != 0 && totalMsgCount % txSize == 0) { - channel.txCommit(); - } - stats.handleSend(); - now = System.currentTimeMillis(); - } - - } catch (IOException e) { - throw new RuntimeException(e); - } catch (InterruptedException e) { - throw new RuntimeException (e); - } - } - - private void publish(byte[] msg) - throws IOException { - - unconfirmedSet.add(channel.getNextPublishSeqNo()); - channel.basicPublish(exchangeName, randomRoutingKey ? UUID.randomUUID().toString() : id, - mandatory, immediate, - persistent ? MessageProperties.MINIMAL_PERSISTENT_BASIC : MessageProperties.MINIMAL_BASIC, - msg); - } - - private byte[] createMessage(int sequenceNumber) - throws IOException { - - ByteArrayOutputStream acc = new ByteArrayOutputStream(); - DataOutputStream d = new DataOutputStream(acc); - long nano = System.nanoTime(); - d.writeInt(sequenceNumber); - d.writeLong(nano); - d.flush(); - acc.flush(); - byte[] m = acc.toByteArray(); - if (m.length <= message.length) { - System.arraycopy(m, 0, message, 0, m.length); - return message; - } else { - return m; - } - } - -} diff --git a/test/src/com/rabbitmq/examples/perf/ProducerConsumerBase.java b/test/src/com/rabbitmq/examples/perf/ProducerConsumerBase.java deleted file mode 100644 index ad4bca9ae7..0000000000 --- a/test/src/com/rabbitmq/examples/perf/ProducerConsumerBase.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.rabbitmq.examples.perf; - -/** - * - */ -public class ProducerConsumerBase { - protected float rateLimit; - protected long lastStatsTime; - protected int msgCount; - - protected void delay(long now) { - - long elapsed = now - lastStatsTime; - //example: rateLimit is 5000 msg/s, - //10 ms have elapsed, we have sent 200 messages - //the 200 msgs we have actually sent should have taken us - //200 * 1000 / 5000 = 40 ms. So we pause for 40ms - 10ms - long pause = (long) (rateLimit == 0.0f ? - 0.0f : (msgCount * 1000.0 / rateLimit - elapsed)); - if (pause > 0) { - try { - Thread.sleep(pause); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } -} diff --git a/test/src/com/rabbitmq/examples/perf/RateVsLatencyScenario.java b/test/src/com/rabbitmq/examples/perf/RateVsLatencyScenario.java deleted file mode 100644 index 4692d54fbf..0000000000 --- a/test/src/com/rabbitmq/examples/perf/RateVsLatencyScenario.java +++ /dev/null @@ -1,55 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import com.rabbitmq.client.ConnectionFactory; - -public class RateVsLatencyScenario implements Scenario { - private final String name; - private final ConnectionFactory factory; - private final MulticastParams params; - private VaryingScenario impl; - - public RateVsLatencyScenario(String name, ConnectionFactory factory, MulticastParams params) { - this.name = name; - this.factory = factory; - this.params = params; - } - - public void run() throws Exception { - SimpleScenario s = new SimpleScenario("untitled", factory, params); - s.run(); - SimpleScenarioStats m = s.getStats(); - int maxRate = (int) (m.getRecvRate() + m.getSendRate()) / 2; - Double[] factors = new Double[]{0.01, 0.2, 0.4, 0.6, 0.8, 0.9, 0.95, 0.96, 0.97, 0.98, 0.99, 1.0, 1.01, 1.02, 1.03, 1.04, 1.05}; - Integer [] rates = new Integer[factors.length]; - for (int i = 0; i < rates.length; i++) { - rates[i] = (int) (factors[i] * maxRate); - } - impl = new VaryingScenario("untitled", factory, params, - new MulticastVariable("producerRateLimit", (Object[]) rates)); - impl.run(); - } - - public ScenarioStats getStats() { - return impl.getStats(); - } - - public String getName() { - return name; - } -} diff --git a/test/src/com/rabbitmq/examples/perf/Scenario.java b/test/src/com/rabbitmq/examples/perf/Scenario.java deleted file mode 100644 index 63c841d510..0000000000 --- a/test/src/com/rabbitmq/examples/perf/Scenario.java +++ /dev/null @@ -1,23 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -public interface Scenario { - public String getName(); - public void run() throws Exception; - public ScenarioStats getStats(); -} diff --git a/test/src/com/rabbitmq/examples/perf/ScenarioFactory.java b/test/src/com/rabbitmq/examples/perf/ScenarioFactory.java deleted file mode 100644 index cb1e536dbf..0000000000 --- a/test/src/com/rabbitmq/examples/perf/ScenarioFactory.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.rabbitmq.examples.perf; - -import com.rabbitmq.client.ConnectionFactory; - -import java.util.List; -import java.util.Map; - -public class ScenarioFactory { - public static Scenario fromJSON(Map json, ConnectionFactory factory) { - String uri = "amqp://localhost"; - String type = read("type", json, String.class); - String name = read("name", json, String.class); - Integer interval = read("interval", json, Integer.class, 1000); - List paramsJSON = read("params", json, List.class); - - try { - uri = read("uri", json, String.class); - factory.setUri(uri); - } catch(Exception e) { - throw new RuntimeException("scenario: " + name + " with malformed uri: " - + uri + " - " + e.getMessage()); - } - - MulticastParams[] params = new MulticastParams[paramsJSON.size()]; - for (int i = 0; i < paramsJSON.size(); i++) { - params[i] = paramsFromJSON((Map) paramsJSON.get(i)); - } - - if (type.equals("simple")) { - return new SimpleScenario(name, factory, interval, params); - } - else if (type.equals("rate-vs-latency")) { - return new RateVsLatencyScenario(name, factory, params[0]); // TODO - } - else if (type.equals("varying")) { - List variablesJSON = read("variables", json, List.class); - Variable[] variables = new Variable[variablesJSON.size()]; - for (int i = 0; i < variablesJSON.size(); i++) { - variables[i] = variableFromJSON((Map) variablesJSON.get(i)); - } - - return new VaryingScenario(name, factory, params, variables); - } - - throw new RuntimeException("Type " + type + " was not simple or varying."); - } - - private static T read(String key, Map map, Class clazz) { - if (map.containsKey(key)) { - return read0(key, map, clazz); - } - else { - throw new RuntimeException("Key " + key + " not found."); - } - } - - private static T read(String key, Map map, Class clazz, T def) { - if (map.containsKey(key)) { - return read0(key, map, clazz); - } - else { - return def; - } - } - - @SuppressWarnings("unchecked") - private static T read0(String key, Map map, Class clazz) { - Object o = map.get(key); - if (clazz.isAssignableFrom(o.getClass())) { - return (T) o; - } - else { - throw new RuntimeException("Object under key " + key + " was a " + o.getClass() + ", not a " + clazz + "."); - } - } - - private static MulticastParams paramsFromJSON(Map json) { - MulticastParams params = new MulticastParams(); - params.setAutoDelete(true); - for (Object key : json.keySet()) { - PerfUtil.setValue(params, hyphensToCamel((String)key), json.get(key)); - } - return params; - } - - private static Variable variableFromJSON(Map json) { - String type = read("type", json, String.class, "multicast"); - String name = read("name", json, String.class); - Object[] values = read("values", json, List.class).toArray(); - - if (type.equals("multicast")) { - return new MulticastVariable(hyphensToCamel(name), values); - } - - throw new RuntimeException("Type " + type + " was not multicast"); - } - - private static String hyphensToCamel(String name) { - String out = ""; - for (String part : name.split("-")) { - out += part.substring(0, 1).toUpperCase() + part.substring(1); - } - return out.substring(0, 1).toLowerCase() + out.substring(1); - } -} diff --git a/test/src/com/rabbitmq/examples/perf/ScenarioStats.java b/test/src/com/rabbitmq/examples/perf/ScenarioStats.java deleted file mode 100644 index d18126ca3d..0000000000 --- a/test/src/com/rabbitmq/examples/perf/ScenarioStats.java +++ /dev/null @@ -1,23 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import java.util.Map; - -public interface ScenarioStats { - public Map results(); -} diff --git a/test/src/com/rabbitmq/examples/perf/SimpleScenario.java b/test/src/com/rabbitmq/examples/perf/SimpleScenario.java deleted file mode 100644 index 70d4145e48..0000000000 --- a/test/src/com/rabbitmq/examples/perf/SimpleScenario.java +++ /dev/null @@ -1,57 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import com.rabbitmq.client.ConnectionFactory; - -import java.io.IOException; - -public class SimpleScenario implements Scenario { - private final String name; - private final ConnectionFactory factory; - private final MulticastParams[] params; - private final long interval; - private SimpleScenarioStats stats; - - public SimpleScenario(String name, ConnectionFactory factory, MulticastParams... params) { - this(name, factory, 1000L, params); - } - - public SimpleScenario(String name, ConnectionFactory factory, long interval, MulticastParams... params) { - this.name = name; - this.factory = factory; - this.params = params; - this.interval = interval; - } - - public void run() throws IOException, InterruptedException { - this.stats = new SimpleScenarioStats(interval); - for (MulticastParams p : params) { - MulticastSet set = new MulticastSet(stats, factory, p); - stats.setup(p); - set.run(); - } - } - - public SimpleScenarioStats getStats() { - return stats; - } - - public String getName() { - return name; - } -} diff --git a/test/src/com/rabbitmq/examples/perf/SimpleScenarioStats.java b/test/src/com/rabbitmq/examples/perf/SimpleScenarioStats.java deleted file mode 100644 index 0a6e9c5b9c..0000000000 --- a/test/src/com/rabbitmq/examples/perf/SimpleScenarioStats.java +++ /dev/null @@ -1,94 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -class SimpleScenarioStats extends Stats implements ScenarioStats { - private static final int IGNORE_FIRST = 3; - - private final List> samples = new ArrayList>(); - private long elapsedTotalToIgnore; - private long minMsgSize; - - public SimpleScenarioStats(long interval) { - super(interval); - } - - protected void report(long now) { - if (samples.size() == IGNORE_FIRST) { - cumulativeLatencyTotal = 0; - latencyCountTotal = 0; - sendCountTotal = 0; - recvCountTotal = 0; - elapsedTotalToIgnore = elapsedTotal; - } - - Map sample = new HashMap(); - sample.put("send-msg-rate", rate(sendCountInterval, elapsedInterval)); - sample.put("send-bytes-rate", rate(sendCountInterval, elapsedInterval) * minMsgSize); - sample.put("recv-msg-rate", rate(recvCountInterval, elapsedInterval)); - sample.put("recv-bytes-rate", rate(recvCountInterval, elapsedInterval) * minMsgSize); - sample.put("elapsed", elapsedTotal); - if (latencyCountInterval > 0) { - sample.put("avg-latency", intervalAverageLatency()); - sample.put("min-latency", minLatency / 1000L); - sample.put("max-latency", maxLatency / 1000L); - } - samples.add(sample); - } - - public Map results() { - Map map = new HashMap(); - map.put("send-msg-rate", getSendRate()); - map.put("send-bytes-rate", getSendRate() * minMsgSize); - map.put("recv-msg-rate", getRecvRate()); - map.put("recv-bytes-rate", getRecvRate() * minMsgSize); - if (latencyCountTotal > 0) { - map.put("avg-latency", overallAverageLatency()); - } - map.put("samples", samples); - return map; - } - - public void setup(MulticastParams params) { - this.minMsgSize = params.getMinMsgSize(); - } - - public double getSendRate() { - return rate(sendCountTotal, elapsedTotal - elapsedTotalToIgnore); - } - - public double getRecvRate() { - return rate(recvCountTotal, elapsedTotal - elapsedTotalToIgnore); - } - - private double rate(long count, long elapsed) { - return elapsed == 0 ? 0.0 : (1000.0 * count / elapsed); - } - - private long overallAverageLatency() { - return cumulativeLatencyTotal / (1000L * latencyCountTotal); - } - - private long intervalAverageLatency() { - return cumulativeLatencyInterval / (1000L * latencyCountInterval); - } -} diff --git a/test/src/com/rabbitmq/examples/perf/Stats.java b/test/src/com/rabbitmq/examples/perf/Stats.java deleted file mode 100644 index ea7547824e..0000000000 --- a/test/src/com/rabbitmq/examples/perf/Stats.java +++ /dev/null @@ -1,113 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -public abstract class Stats { - protected final long interval; - - protected final long startTime; - protected long lastStatsTime; - - protected int sendCountInterval; - protected int returnCountInterval; - protected int confirmCountInterval; - protected int nackCountInterval; - protected int recvCountInterval; - - protected int sendCountTotal; - protected int recvCountTotal; - - protected int latencyCountInterval; - protected int latencyCountTotal; - protected long minLatency; - protected long maxLatency; - protected long cumulativeLatencyInterval; - protected long cumulativeLatencyTotal; - - protected long elapsedInterval; - protected long elapsedTotal; - - public Stats(long interval) { - this.interval = interval; - startTime = System.currentTimeMillis(); - reset(startTime); - } - - private void reset(long t) { - lastStatsTime = t; - - sendCountInterval = 0; - returnCountInterval = 0; - confirmCountInterval = 0; - nackCountInterval = 0; - recvCountInterval = 0; - - minLatency = Long.MAX_VALUE; - maxLatency = Long.MIN_VALUE; - latencyCountInterval = 0; - cumulativeLatencyInterval = 0L; - } - - private void report() { - long now = System.currentTimeMillis(); - elapsedInterval = now - lastStatsTime; - - if (elapsedInterval >= interval) { - elapsedTotal += elapsedInterval; - report(now); - reset(now); - } - } - - protected abstract void report(long now); - - public synchronized void handleSend() { - sendCountInterval++; - sendCountTotal++; - report(); - } - - public synchronized void handleReturn() { - returnCountInterval++; - report(); - } - - public synchronized void handleConfirm(int numConfirms) { - confirmCountInterval +=numConfirms; - report(); - } - - public synchronized void handleNack(int numAcks) { - nackCountInterval +=numAcks; - report(); - } - - public synchronized void handleRecv(long latency) { - recvCountInterval++; - recvCountTotal++; - if (latency > 0) { - minLatency = Math.min(minLatency, latency); - maxLatency = Math.max(maxLatency, latency); - cumulativeLatencyInterval += latency; - cumulativeLatencyTotal += latency; - latencyCountInterval++; - latencyCountTotal++; - } - report(); - } - -} diff --git a/test/src/com/rabbitmq/examples/perf/Variable.java b/test/src/com/rabbitmq/examples/perf/Variable.java deleted file mode 100644 index 771c607ec9..0000000000 --- a/test/src/com/rabbitmq/examples/perf/Variable.java +++ /dev/null @@ -1,23 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import java.util.List; - -public interface Variable { - public List getValues(); -} diff --git a/test/src/com/rabbitmq/examples/perf/VariableValue.java b/test/src/com/rabbitmq/examples/perf/VariableValue.java deleted file mode 100644 index d76bfa5a72..0000000000 --- a/test/src/com/rabbitmq/examples/perf/VariableValue.java +++ /dev/null @@ -1,25 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -public interface VariableValue { - public void setup(MulticastParams params) throws Exception; - public void teardown(MulticastParams params); - - public String getName(); - public Object getValue(); -} diff --git a/test/src/com/rabbitmq/examples/perf/VaryingScenario.java b/test/src/com/rabbitmq/examples/perf/VaryingScenario.java deleted file mode 100644 index 1eafa38bf1..0000000000 --- a/test/src/com/rabbitmq/examples/perf/VaryingScenario.java +++ /dev/null @@ -1,89 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import com.rabbitmq.client.ConnectionFactory; - -import java.util.ArrayList; -import java.util.List; - -public class VaryingScenario implements Scenario { - private final String name; - private final ConnectionFactory factory; - private final MulticastParams[] params; - private final VaryingScenarioStats stats = new VaryingScenarioStats(); - private final Variable[] variables; - - public VaryingScenario(String name, ConnectionFactory factory, - MulticastParams params, Variable... variables) { - this(name, factory, new MulticastParams[]{params}, variables); - } - - public VaryingScenario(String name, ConnectionFactory factory, - MulticastParams[] params, Variable... variables) { - this.name = name; - this.factory = factory; - this.params = params; - this.variables = variables; - } - - public void run() throws Exception { - run(variables, new ArrayList()); - } - - private void run(Variable[] variables, List values) throws Exception { - if (variables.length > 0) { - Variable variable = variables[0]; - Variable[] rest = rest(variables); - for (VariableValue value : variable.getValues()) { - List values2 = new ArrayList(values); - values2.add(value); - run(rest, values2); - } - } - else { - SimpleScenarioStats stats0 = stats.next(values); - for (MulticastParams p : params) { - for (VariableValue value : values) { - value.setup(p); - } - MulticastSet set = new MulticastSet(stats0, factory, p); - stats0.setup(p); - set.run(); - for (VariableValue value : values) { - value.teardown(p); - } - } - System.out.print("#"); - System.out.flush(); - } - } - - private Variable[] rest(Variable[] variables) { - Variable[] tail = new Variable[variables.length - 1]; - System.arraycopy(variables, 1, tail, 0, tail.length); - return tail; - } - - public ScenarioStats getStats() { - return stats; - } - - public String getName() { - return name; - } -} diff --git a/test/src/com/rabbitmq/examples/perf/VaryingScenarioStats.java b/test/src/com/rabbitmq/examples/perf/VaryingScenarioStats.java deleted file mode 100644 index f7f97e0abe..0000000000 --- a/test/src/com/rabbitmq/examples/perf/VaryingScenarioStats.java +++ /dev/null @@ -1,86 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - -package com.rabbitmq.examples.perf; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class VaryingScenarioStats implements ScenarioStats { - private final Map, SimpleScenarioStats> stats = new HashMap, SimpleScenarioStats>(); - private final List> keys = new ArrayList>(); - - public VaryingScenarioStats() {} - - public SimpleScenarioStats next(List value) { - SimpleScenarioStats stats = new SimpleScenarioStats(1000L); - keys.add(value); - this.stats.put(value, stats); - return stats; - } - - @SuppressWarnings("unchecked") - public Map results() { - Map map = new HashMap(); - - List dimensions = new ArrayList(); - for (VariableValue keyElem : keys.get(0)) { - dimensions.add(keyElem.getName()); - } - map.put("dimensions", dimensions); - - Map> dimensionValues = new HashMap>(); - for (List key : keys) { - for (VariableValue elem : key) { - List values = get(elem.getName(), dimensionValues, new ArrayList()); - String value = elem.getValue().toString(); - if (!values.contains(value)) { - values.add(value); - } - } - } - map.put("dimension-values", dimensionValues); - - Map data = new HashMap(); - for (List key : keys) { - Map results = stats.get(key).results(); - Map node = data; - for (int i = 0; i < key.size(); i++) { - VariableValue elem = key.get(i); - if (i == key.size() - 1) { - node.put(elem.getValue().toString(), results); - } - else { - node = (Map) get(elem.getValue().toString(), node, new HashMap()); - } - } - } - map.put("data", data); - - return map; - } - - private V get(K key, Map map, V def) { - V val = map.get(key); - if (val == null) { - val = def; - map.put(key, val); - } - return val; - } -} diff --git a/test/src/com/rabbitmq/tools/Host.java b/test/src/com/rabbitmq/tools/Host.java deleted file mode 100644 index ef8bb41084..0000000000 --- a/test/src/com/rabbitmq/tools/Host.java +++ /dev/null @@ -1,171 +0,0 @@ -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in -// compliance with the License. You may obtain a copy of the License -// at http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" -// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -// the License for the specific language governing rights and -// limitations under the License. -// -// The Original Code is RabbitMQ. -// -// The Initial Developer of the Original Code is GoPivotal, Inc. -// Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -// - - -package com.rabbitmq.tools; - -import com.rabbitmq.client.impl.NetworkConnection; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class Host { - - private static String capture(InputStream is) - throws IOException - { - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - String line; - StringBuilder buff = new StringBuilder(); - while ((line = br.readLine()) != null) { - buff.append(line).append("\n"); - } - return buff.toString(); - } - - public static Process executeCommand(String command) throws IOException - { - Process pr = executeCommandProcess(command); - - int ev = waitForExitValue(pr); - if (ev != 0) { - String stdout = capture(pr.getInputStream()); - String stderr = capture(pr.getErrorStream()); - throw new IOException("unexpected command exit value: " + ev + - "\ncommand: " + command + "\n" + - "\nstdout:\n" + stdout + - "\nstderr:\n" + stderr + "\n"); - } - return pr; - } - - private static int waitForExitValue(Process pr) { - while(true) { - try { - pr.waitFor(); - break; - } catch (InterruptedException ignored) {} - } - return pr.exitValue(); - } - - public static Process executeCommandIgnoringErrors(String command) throws IOException - { - Process pr = executeCommandProcess(command); - waitForExitValue(pr); - return pr; - } - - private static Process executeCommandProcess(String command) throws IOException - { - String[] finalCommand; - if (System.getProperty("os.name").toLowerCase().contains("windows")) { - finalCommand = new String[4]; - finalCommand[0] = "C:\\winnt\\system32\\cmd.exe"; - finalCommand[1] = "/y"; - finalCommand[2] = "/c"; - finalCommand[3] = command; - } else { - finalCommand = new String[3]; - finalCommand[0] = "/bin/sh"; - finalCommand[1] = "-c"; - finalCommand[2] = command; - } - return Runtime.getRuntime().exec(finalCommand); - } - - public static Process rabbitmqctl(String command) throws IOException { - return executeCommand("../rabbitmq-server/scripts/rabbitmqctl " + command); - } - - public static Process rabbitmqctlIgnoreErrors(String command) throws IOException { - return executeCommandIgnoringErrors("../rabbitmq-server/scripts/rabbitmqctl " + command); - } - - public static Process invokeMakeTarget(String command) throws IOException { - return executeCommand("cd ../rabbitmq-test; " + makeCommand() + " " + command); - } - - private static String makeCommand() - { - // Get the make(1) executable to use from the environment: - // make(1) provides the path to itself in $MAKE. - String makecmd = System.getenv("MAKE"); - - // Default to "make" if the environment variable is unset. - if (makecmd == null) { - makecmd = "make"; - } - - return makecmd; - } - - public static void closeConnection(String pid) throws IOException { - rabbitmqctl("close_connection '" + pid + "' 'Closed via rabbitmqctl'"); - } - - public static void closeConnection(NetworkConnection c) throws IOException { - Host.ConnectionInfo ci = findConnectionInfoFor(Host.listConnections(), c); - closeConnection(ci.getPid()); - } - - public static class ConnectionInfo { - private final String pid; - private final int peerPort; - - public ConnectionInfo(String pid, int peerPort) { - this.pid = pid; - this.peerPort = peerPort; - } - - public String getPid() { - return pid; - } - - public int getPeerPort() { - return peerPort; - } - } - - public static List listConnections() throws IOException { - String output = capture(rabbitmqctl("list_connections -q pid peer_port").getInputStream()); - String[] allLines = output.split("\n"); - - ArrayList result = new ArrayList(); - for (String line : allLines) { - // line: 58713 - String[] columns = line.split("\t"); - result.add(new ConnectionInfo(columns[0], Integer.valueOf(columns[1]))); - } - return result; - } - - private static Host.ConnectionInfo findConnectionInfoFor(List xs, NetworkConnection c) { - Host.ConnectionInfo result = null; - for (Host.ConnectionInfo ci : xs) { - if(c.getLocalPort() == ci.getPeerPort()){ - result = ci; - break; - } - } - return result; - } -}