├── .mvn ├── maven.config └── wrapper │ └── maven-wrapper.properties ├── .gitignore ├── ci ├── rabbitmq_delayed_message_exchange-4.0.7.ez ├── rabbitmq_delayed_message_exchange-4.1.0.ez ├── rabbitmq_delayed_message_exchange-4.2.0.ez ├── release-jms-client.sh ├── publish-documentation-to-github-pages.sh └── start-broker.sh ├── release-versions.txt ├── src ├── test │ ├── resources │ │ └── logback-test.xml │ └── java │ │ ├── com │ │ └── rabbitmq │ │ │ ├── integration │ │ │ └── tests │ │ │ │ ├── ShellIT.java │ │ │ │ ├── AbstractITQueueSSL.java │ │ │ │ ├── AbstractTestConnectionFactory.java │ │ │ │ ├── AbstractITLimitedQueue.java │ │ │ │ ├── AbstractITTopicSSL.java │ │ │ │ ├── AbstractITTopic.java │ │ │ │ ├── AbstractAmqpITQueue.java │ │ │ │ ├── DelayedTopicMessageIT.java │ │ │ │ ├── SSLSimpleTopicMessageIT.java │ │ │ │ ├── DelayedAMQPQueueMessageIT.java │ │ │ │ ├── ExceptionHandlingIT.java │ │ │ │ ├── RmqDestinationIT.java │ │ │ │ ├── RabbitAPIConnectionFactory.java │ │ │ │ ├── SelectedTopicMessageIdentifierIT.java │ │ │ │ ├── TemporaryQueueIT.java │ │ │ │ ├── AmqpPropertiesCustomiserIT.java │ │ │ │ ├── ReceiveNoWaitIT.java │ │ │ │ ├── SendingContextConsumerIT.java │ │ │ │ ├── SimpleQueueMessageDefaultsIT.java │ │ │ │ ├── SSLSimpleQueueMessageIT.java │ │ │ │ ├── SimpleQueueMessageIT.java │ │ │ │ ├── AbstractITQueue.java │ │ │ │ ├── NamingStrategyIT.java │ │ │ │ ├── RabbitMQRedeliverOnNackIT.java │ │ │ │ ├── TemporaryTopicIT.java │ │ │ │ ├── SSLHostnameVerificationIT.java │ │ │ │ ├── SimpleSendReceiveTransactionIT.java │ │ │ │ └── ReceivingContextConsumerIT.java │ │ │ └── jms │ │ │ ├── client │ │ │ ├── SessionParamsTest.java │ │ │ ├── ConnectionParamsTest.java │ │ │ ├── UtilsTest.java │ │ │ ├── DefaultReplyToStrategyTest.java │ │ │ └── HandleAnyReplyToStrategyTest.java │ │ │ ├── util │ │ │ ├── TestUtil.java │ │ │ └── TestTimeTracker.java │ │ │ └── parse │ │ │ └── sql │ │ │ ├── SqlCompilerTest.java │ │ │ ├── SqlTypeCheckerTest.java │ │ │ └── SqlTokenStreamTest.java │ │ └── SanityCheck.java ├── docs │ └── asciidoc │ │ ├── asynchronous-sending.adoc │ │ └── logging.adoc └── main │ └── java │ └── com │ └── rabbitmq │ └── jms │ ├── util │ ├── Util.java │ ├── RMQJMSException.java │ ├── RMQJMSSecurityException.java │ ├── RMQMessageFormatException.java │ ├── IteratorEnum.java │ ├── RMQJMSSelectorException.java │ ├── Abortable.java │ ├── AbortedException.java │ ├── DiscardingObjectOutput.java │ ├── AbortableHolder.java │ └── HexDisplay.java │ ├── client │ ├── PublishingListener.java │ ├── ReceivingContext.java │ ├── AuthenticationMechanism.java │ ├── RMQMessageListenerExecutionJMSException.java │ ├── SendingContext.java │ ├── ReplyToStrategy.java │ ├── PublisherConfirmContext.java │ ├── Subscriptions.java │ ├── DefaultReplyToStrategy.java │ ├── SendingContextConsumer.java │ ├── ReceivingContextConsumer.java │ ├── HandleAnyReplyToStrategy.java │ ├── RMQNullMessage.java │ ├── JMSMetaData.java │ ├── GenericVersion.java │ ├── BrowsingMessageQueue.java │ ├── DelayedReceiver.java │ ├── RMQConnectionMetaData.java │ ├── BrowsingConsumer.java │ └── DelayedMessageService.java │ ├── parse │ ├── sql │ │ ├── SqlTokenValueType.java │ │ ├── SqlTypeChecker.java │ │ ├── SqlExpressionType.java │ │ ├── SqlTreeNode.java │ │ ├── SqlCompiler.java │ │ ├── SqlParser.java │ │ ├── SqlEvaluator.java │ │ ├── SqlParseTree.java │ │ ├── SqlExpressionValue.java │ │ └── SqlTreeType.java │ ├── Parser.java │ ├── Compiler.java │ ├── Multiples.java │ ├── Evaluator.java │ ├── ParseTree.java │ ├── Visitor.java │ ├── TokenStream.java │ └── ParseTreeTraverser.java │ └── admin │ ├── DefaultNamingStrategy.java │ └── NamingStrategy.java ├── LICENSE ├── .github ├── workflows │ ├── publish-documentation.yml │ ├── publish-snapshot.yml │ ├── test-supported-java-versions-3.x.yml │ ├── test-supported-java-versions-2.x.yml │ ├── test-alphas.yml │ ├── release.yml │ └── test.yml └── dependabot.yml ├── .travis.yml ├── Makefile ├── Changelog.md ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Dmaven.wagon.http.retryHandler.count=10 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .classpath 3 | .project 4 | .settings 5 | .idea/* 6 | *.iml 7 | enabled_plugins 8 | *.ez 9 | -------------------------------------------------------------------------------- /ci/rabbitmq_delayed_message_exchange-4.0.7.ez: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rabbitmq/rabbitmq-jms-client/HEAD/ci/rabbitmq_delayed_message_exchange-4.0.7.ez -------------------------------------------------------------------------------- /ci/rabbitmq_delayed_message_exchange-4.1.0.ez: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rabbitmq/rabbitmq-jms-client/HEAD/ci/rabbitmq_delayed_message_exchange-4.1.0.ez -------------------------------------------------------------------------------- /ci/rabbitmq_delayed_message_exchange-4.2.0.ez: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rabbitmq/rabbitmq-jms-client/HEAD/ci/rabbitmq_delayed_message_exchange-4.2.0.ez -------------------------------------------------------------------------------- /release-versions.txt: -------------------------------------------------------------------------------- 1 | RELEASE_VERSION="3.5.0" 2 | DEVELOPMENT_VERSION="3.6.0-SNAPSHOT" 3 | RELEASE_BRANCH="main" 4 | API_FAMILY="3.x" 5 | JAVA_VERSION=11 6 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/docs/asciidoc/asynchronous-sending.adoc: -------------------------------------------------------------------------------- 1 | 2 | == Asynchronous Sending 3 | 4 | The JMS client supports https://jakarta.ee/specifications/messaging/3.1/jakarta-messaging-spec-3.1.html#sending-messages-asynchronously-jms_spec-43[asynchronous sending] by using RabbitMQ link:https://rabbitmq.com/confirms.html#publisher-confirms[publisher confirms] mechanism. 5 | 6 | Note the `CompletionListener` is not a good place to execute long-running tasks. 7 | Those should be executed in a dedicated thread, using e.g. an `ExecutorService`. -------------------------------------------------------------------------------- /src/docs/asciidoc/logging.adoc: -------------------------------------------------------------------------------- 1 | == Configuring Logging for the JMS Client 2 | 3 | The JMS Client logs messages using SLF4J (Simple Logging Façade for Java). 4 | SLF4J delegates to a logging framework, such as https://logback.qos.ch/[Logback]. 5 | If no other logging framework is 6 | enabled, SLF4J defaults to a built-in, no-op, logger. 7 | See the http://www.slf4j.org/docs.html[SLF4J] documentation for a 8 | list of the logging frameworks SLF4J supports. 9 | 10 | We highly recommend to use a dependency management tool like http://maven.apache.org/[Maven] 11 | or https://gradle.org/[Gradle] to manage dependencies. 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This package, the RabbitMQ JMS client library, is double-licensed 2 | under the Mozilla Public License 2.0 ("MPL") and the Apache License 3 | version 2 ("ASL"). For the MPL, please see LICENSE-MPL-RabbitMQ. For 4 | the ASL, please see LICENSE-APACHE2. 5 | 6 | The RabbitMQ JMS client library depends on third-party software under 7 | the ASL, MPL, and BSD- and MIT-style licenses. For more details, 8 | please see LICENSES.txt. For attribution of copyright and other 9 | details of provenance, please refer to the source code. 10 | 11 | If you have any questions regarding licensing, please contact us at 12 | info@rabbitmq.com. 13 | -------------------------------------------------------------------------------- /.github/workflows/publish-documentation.yml: -------------------------------------------------------------------------------- 1 | name: Publish documentation 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-24.04 8 | 9 | steps: 10 | - uses: actions/checkout@v6 11 | - name: Set up JDK 12 | uses: actions/setup-java@v5 13 | with: 14 | distribution: 'temurin' 15 | java-version: '25' 16 | cache: 'maven' 17 | - name: Generate documentation 18 | run: make doc 19 | - name: Publish Documentation 20 | run: | 21 | git config user.name "rabbitmq-ci" 22 | git config user.email "rabbitmq-ci@users.noreply.github.com" 23 | ci/publish-documentation-to-github-pages.sh 24 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/util/Util.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.util; 3 | 4 | import java.util.UUID; 5 | 6 | /** 7 | * Utility class which generates unique string identifiers. 8 | */ 9 | public final class Util { 10 | /** 11 | * Generates a UUID string identifier. 12 | * @param prefix - prefix of uniquely generated string; may be null, in which case “null” is the prefix used. 13 | * @return a UUID string with given prefix 14 | */ 15 | public static final String generateUUID(String prefix) { 16 | return new StringBuilder().append(prefix).append(UUID.randomUUID()).toString(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/PublishingListener.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import jakarta.jms.CompletionListener; 9 | import jakarta.jms.Message; 10 | 11 | /** 12 | * Internal interface to notify about a published message when publisher confirms are enabled. 13 | * 14 | * @since 1.13.0 15 | */ 16 | interface PublishingListener { 17 | 18 | void publish(Message message, CompletionListener completionListener, long sequenceNumber); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/util/RMQJMSException.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.util; 3 | 4 | import jakarta.jms.JMSException; 5 | 6 | /** 7 | * Wraps an exception as a {@link JMSException}. 8 | */ 9 | public class RMQJMSException extends JMSException { 10 | /** Default version ID */ 11 | private static final long serialVersionUID = 1L; 12 | 13 | public RMQJMSException(String msg, Throwable x) { 14 | this(msg, null, x); 15 | } 16 | 17 | public RMQJMSException(Throwable x) { 18 | this(x.getMessage(), x); 19 | } 20 | 21 | private RMQJMSException(String msg, String errorCode, Throwable x) { 22 | super(msg, errorCode); 23 | this.initCause(x); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/ShellIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2014-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | import com.rabbitmq.jms.util.Shell; 11 | 12 | /* ShellIT tests that Shell.java correctly issues commands */ 13 | public class ShellIT extends AbstractITQueue { 14 | 15 | @Test 16 | public void issueSimpleCommand() throws Exception { 17 | String result = Shell.executeSimpleCommand("pwd"); 18 | System.out.println("Result of pwd is '"+result+"'"); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/util/RMQJMSSecurityException.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.util; 3 | 4 | import jakarta.jms.JMSSecurityException; 5 | 6 | /** 7 | * Wraps an exception as a {@link JMSSecurityException}. 8 | */ 9 | public class RMQJMSSecurityException extends JMSSecurityException { 10 | /** TODO */ 11 | private static final long serialVersionUID = 1L; 12 | 13 | public RMQJMSSecurityException(String msg, Exception x) { 14 | this(msg, null, x); 15 | } 16 | 17 | public RMQJMSSecurityException(Exception x) { 18 | this(x.getMessage(), x); 19 | } 20 | 21 | private RMQJMSSecurityException(String msg, String errorCode, Exception x) { 22 | super(msg, errorCode); 23 | this.initCause(x); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/util/RMQMessageFormatException.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.util; 3 | 4 | import jakarta.jms.MessageFormatException; 5 | 6 | /** 7 | * Wraps an exception as a JMS {@link MessageFormatException}. 8 | */ 9 | public class RMQMessageFormatException extends MessageFormatException { 10 | /** TODO */ 11 | private static final long serialVersionUID = 1L; 12 | 13 | public RMQMessageFormatException(String msg, Exception x) { 14 | this(msg, null, x); 15 | } 16 | 17 | public RMQMessageFormatException(Exception x) { 18 | this(x.getMessage(), x); 19 | } 20 | 21 | private RMQMessageFormatException(String msg, String errorCode, Exception x) { 22 | super(msg, errorCode); 23 | this.initCause(x); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/sql/SqlTokenValueType.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse.sql; 3 | 4 | /** 5 | * The types of the values associated with {@link SqlToken}s and sub-expressions. 6 | *

7 | * Each token can have an associated {@link String} which is interpreted according to the {@link SqlTokenValueType} term 8 | * associated with its {@link SqlTokenType}. 9 | *

10 | *

11 | * The NO_VALUE value indicates that there is no String associated with the token (it is a literal term standing for 12 | * itself, for example the left parenthesis (, or the keyword NOT. 13 | *

14 | */ 15 | enum SqlTokenValueType { 16 | NO_VALUE, 17 | STRING, 18 | IDENT, 19 | LONG, 20 | HEX, 21 | FLOAT, 22 | LIST, 23 | BOOL; 24 | } 25 | -------------------------------------------------------------------------------- /ci/release-jms-client.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source ./release-versions.txt 4 | git checkout $RELEASE_BRANCH 5 | 6 | ./mvnw release:clean release:prepare -DdryRun=true -Darguments="-DskipTests" --no-transfer-progress \ 7 | --batch-mode -Dtag="v$RELEASE_VERSION" \ 8 | -DreleaseVersion=$RELEASE_VERSION \ 9 | -DdevelopmentVersion=$DEVELOPMENT_VERSION \ 10 | 11 | ./mvnw release:clean release:prepare -Darguments="-DskipTests" --no-transfer-progress \ 12 | --batch-mode -Dtag="v$RELEASE_VERSION" \ 13 | -DreleaseVersion=$RELEASE_VERSION \ 14 | -DdevelopmentVersion=$DEVELOPMENT_VERSION 15 | 16 | git checkout "v$RELEASE_VERSION" 17 | 18 | if [[ $RELEASE_VERSION == *[RCM]* ]] 19 | then 20 | MAVEN_PROFILE="milestone" 21 | echo "prerelease=true" >> $GITHUB_ENV 22 | else 23 | MAVEN_PROFILE="release" 24 | echo "prerelease=false" >> $GITHUB_ENV 25 | fi 26 | 27 | ./mvnw clean deploy -P $MAVEN_PROFILE -DskipTests --no-transfer-progress -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/AbstractITQueueSSL.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import jakarta.jms.QueueConnectionFactory; 9 | 10 | import org.junit.jupiter.api.BeforeEach; 11 | 12 | public abstract class AbstractITQueueSSL extends AbstractITQueue { 13 | 14 | @BeforeEach 15 | public void beforeTests() throws Exception { 16 | this.connFactory = (QueueConnectionFactory) AbstractTestConnectionFactory.getTestConnectionFactory(true, 0) 17 | .getConnectionFactory(); 18 | this.queueConn = connFactory.createQueueConnection(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/publish-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Publish snapshot 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-24.04 8 | 9 | steps: 10 | - uses: actions/checkout@v6 11 | - name: Set up JDK 12 | uses: actions/setup-java@v5 13 | with: 14 | distribution: 'temurin' 15 | java-version: '25' 16 | cache: 'maven' 17 | server-id: central 18 | server-username: MAVEN_USERNAME 19 | server-password: MAVEN_PASSWORD 20 | gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} 21 | gpg-passphrase: MAVEN_GPG_PASSPHRASE 22 | - name: Publish snapshot 23 | run: ./mvnw clean deploy -Psnapshots -DskipITs -DskipTests --no-transfer-progress 24 | env: 25 | MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }} 26 | MAVEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN }} 27 | MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 28 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/AbstractTestConnectionFactory.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | public abstract class AbstractTestConnectionFactory { 9 | public abstract jakarta.jms.ConnectionFactory getConnectionFactory(); 10 | 11 | public static AbstractTestConnectionFactory getTestConnectionFactory() throws Exception { 12 | return getTestConnectionFactory(false, 0); 13 | } 14 | 15 | public static AbstractTestConnectionFactory getTestConnectionFactory(boolean isSsl, int qbrMax) throws Exception { 16 | return new RabbitAPIConnectionFactory(isSsl, qbrMax); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/ReceivingContext.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import jakarta.jms.Message; 9 | 10 | /** 11 | * Context when receiving message. 12 | * 13 | * @see com.rabbitmq.jms.admin.RMQConnectionFactory#setReceivingContextConsumer(ReceivingContextConsumer) 14 | * @see ReceivingContextConsumer 15 | * @since 1.11.0 16 | */ 17 | public class ReceivingContext { 18 | 19 | private final Message message; 20 | 21 | public ReceivingContext(Message message) { 22 | this.message = message; 23 | } 24 | 25 | public Message getMessage() { 26 | return message; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: bionic 2 | sudo: required 3 | 4 | language: java 5 | jdk: 6 | - openjdk11 7 | 8 | notifications: 9 | email: 10 | - acogoluegnes@pivotal.io 11 | env: 12 | - PYTHON=python3.7 13 | addons: 14 | apt: 15 | sources: 16 | - sourceline: deb https://packages.erlang-solutions.com/ubuntu bionic contrib 17 | key_url: https://packages.erlang-solutions.com/ubuntu/erlang_solutions.asc 18 | - sourceline: 'ppa:deadsnakes/ppa' 19 | 20 | packages: 21 | - esl-erlang=1:22.1.1-1 22 | before_install: 23 | - sudo apt-get update 24 | - sudo apt-get install python3.7 25 | branches: 26 | only: 27 | - main 28 | before_script: 29 | - ./bin/before_build.sh 30 | 31 | script: ./mvnw clean verify -P '!setup-test-node' -Dtravis-ci=true -Drabbitmqctl.bin='rabbitmq/sbin/rabbitmqctl' -Dtest-broker.A.nodename=rabbit@$(hostname) -Dtest-client-cert.password= -Dtest-tls-certs.dir=/tmp/tls-gen/basic 32 | cache: 33 | directories: 34 | - $HOME/.m2 35 | install: true 36 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/util/IteratorEnum.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.util; 3 | 4 | import java.util.Enumeration; 5 | import java.util.Iterator; 6 | 7 | /** 8 | * An implementation of {@link Enumeration} that uses an {@link Iterator}. 9 | * 10 | * @param type of elements enumerated 11 | */ 12 | public class IteratorEnum implements Enumeration { 13 | 14 | final Iterator it; 15 | 16 | /** 17 | * Create an enumeration based upon the supplied iterator. The iterator is not reset. 18 | * 19 | * @param it iterator to use for enumeration 20 | */ 21 | public IteratorEnum(Iterator it) { 22 | this.it = it; 23 | } 24 | 25 | public boolean hasMoreElements() { 26 | return this.it.hasNext(); 27 | } 28 | 29 | public E nextElement() { 30 | return this.it.next(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/util/RMQJMSSelectorException.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.util; 3 | 4 | import jakarta.jms.InvalidSelectorException; 5 | 6 | /** 7 | * Wraps an exception as a {@link InvalidSelectorException}. 8 | */ 9 | public class RMQJMSSelectorException extends InvalidSelectorException { 10 | /** TODO */ 11 | private static final long serialVersionUID = 1L; 12 | 13 | public RMQJMSSelectorException(String msg, Exception x) { 14 | this(msg, null, x); 15 | } 16 | 17 | public RMQJMSSelectorException(Exception x) { 18 | this(x.getMessage(), x); 19 | } 20 | 21 | private RMQJMSSelectorException(String msg, String errorCode, Exception x) { 22 | super(msg, errorCode); 23 | this.initCause(x); 24 | } 25 | 26 | public RMQJMSSelectorException(String msg) { 27 | super(msg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MVN ?= mvn 2 | MVN_FLAGS ?= 3 | 4 | ifndef DEPS_DIR 5 | ifneq ($(wildcard ../../UMBRELLA.md),) 6 | DEPS_DIR = .. 7 | else 8 | DEPS_DIR = deps 9 | endif 10 | endif 11 | 12 | export PATH := $(CURDIR):$(CURDIR)/scripts:$(PATH) 13 | 14 | MVN_FLAGS += -Ddeps.dir="$(abspath $(DEPS_DIR))" 15 | 16 | .PHONY: all deps tests clean distclean 17 | 18 | all: deps 19 | $(MVN) $(MVN_FLAGS) compile 20 | 21 | deps: $(DEPS_DIR)/rabbit 22 | @: 23 | 24 | dist: clean 25 | $(MVN) $(MVN_FLAGS) -DskipTests=true -Dmaven.javadoc.failOnError=false package javadoc:javadoc 26 | 27 | $(DEPS_DIR)/rabbit: 28 | git clone https://github.com/rabbitmq/rabbitmq-server.git $@ 29 | $(MAKE) -C $@ fetch-deps DEPS_DIR="$(abspath $(DEPS_DIR))" 30 | 31 | clean: 32 | $(MVN) $(MVN_FLAGS) clean 33 | 34 | distclean: clean 35 | $(MAKE) -C $(DEPS_DIR)/rabbitmq_codegen clean 36 | 37 | .PHONY: cluster-other-node 38 | 39 | .PHONY: doc 40 | doc: ## Generate PerfTest documentation 41 | @mvnw asciidoctor:process-asciidoc --no-transfer-progress 42 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/AuthenticationMechanism.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | /** 9 | * Authentication mechanisms that the client can use to authenticate to the server. 10 | * 11 | * @see com.rabbitmq.jms.admin.RMQConnectionFactory#setAuthenticationMechanism(AuthenticationMechanism) 12 | */ 13 | public enum AuthenticationMechanism { 14 | 15 | /** 16 | * Authentication mechanism corresponding to {@link com.rabbitmq.client.DefaultSaslConfig#PLAIN} 17 | */ 18 | PLAIN, 19 | /** 20 | * Authentication mechanism corresponding to {@link com.rabbitmq.client.DefaultSaslConfig#EXTERNAL} 21 | */ 22 | EXTERNAL; 23 | } 24 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip 20 | -------------------------------------------------------------------------------- /ci/publish-documentation-to-github-pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . $(pwd)/release-versions.txt 4 | 5 | MESSAGE=$(git log -1 --pretty=%B) 6 | 7 | git checkout -- .mvn/maven.config 8 | RELEASE_VERSION=`./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout` 9 | CURRENT_BRANCH=`git rev-parse --abbrev-ref HEAD` 10 | 11 | git remote set-branches origin 'gh-pages' 12 | git fetch -v 13 | 14 | git checkout gh-pages 15 | mkdir -p ${API_FAMILY}/${RELEASE_VERSION}/htmlsingle 16 | cp target/generated-docs/* ${API_FAMILY}/${RELEASE_VERSION}/htmlsingle 17 | git add ${API_FAMILY}/${RELEASE_VERSION}/ 18 | 19 | if [[ $RELEASE_VERSION == *[RCM]* ]] 20 | then 21 | DOC_DIR="milestone" 22 | elif [[ $RELEASE_VERSION == *SNAPSHOT* ]] 23 | then 24 | DOC_DIR="snapshot" 25 | else 26 | DOC_DIR="stable" 27 | fi 28 | 29 | mkdir -p ${API_FAMILY}/${DOC_DIR}/htmlsingle 30 | cp target/generated-docs/* ${API_FAMILY}/${DOC_DIR}/htmlsingle 31 | git add ${API_FAMILY}/${DOC_DIR}/ 32 | 33 | git commit -m "$MESSAGE" 34 | git push origin gh-pages 35 | git checkout $CURRENT_BRANCH 36 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/AbstractITLimitedQueue.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import jakarta.jms.QueueConnectionFactory; 9 | 10 | import org.junit.jupiter.api.BeforeEach; 11 | 12 | /** The setting queueBrowserReadMax is set to 5 on the connection in this test framework. */ 13 | public abstract class AbstractITLimitedQueue extends AbstractITQueue{ 14 | protected final static int QBR_MAX = 5; 15 | 16 | @BeforeEach 17 | public void beforeTests() throws Exception { 18 | this.connFactory = (QueueConnectionFactory) AbstractTestConnectionFactory.getTestConnectionFactory(false, QBR_MAX) 19 | .getConnectionFactory(); 20 | this.queueConn = connFactory.createQueueConnection(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/util/Abortable.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.util; 3 | 4 | /** 5 | * Classes whose instances can be aborted, stopped and started (from other threads) implement this interface. 6 | */ 7 | public interface Abortable { 8 | /** 9 | * Cause any other implementing threads to terminate fairly quickly, signalling abnormal termination 10 | * to its instigator, if necessary. 11 | *

12 | * Implementations of this method must be thread-safe. 13 | *

14 | */ 15 | void abort() throws Exception; 16 | 17 | /** 18 | * Cause any other implementing threads to stop temporarily. 19 | *

20 | * Implementations of this method must be thread-safe. 21 | *

22 | */ 23 | void stop() throws Exception; 24 | 25 | /** 26 | * Cause any other stopped threads to start. 27 | *

28 | * Implementations of this method must be thread-safe. 29 | *

30 | */ 31 | void start() throws Exception; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/sql/SqlTypeChecker.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse.sql; 3 | 4 | import static com.rabbitmq.jms.parse.ParseTreeTraverser.traverse; 5 | 6 | import java.util.Collections; 7 | import java.util.Map; 8 | 9 | import com.rabbitmq.jms.parse.ParseTree; 10 | 11 | public abstract class SqlTypeChecker { // prevent instantiation directly 12 | private SqlTypeChecker() {} // prevent instantiation indirectly 13 | 14 | public static SqlExpressionType deriveExpressionType(ParseTree tree, Map identifierType) { 15 | if (tree == null) return SqlExpressionType.NOT_SET; // null trees are not of any type 16 | traverse(tree, new SqlTypeSetterVisitor(identifierType)); 17 | return tree.getNode().getExpValue().getType(); 18 | } 19 | 20 | public static SqlExpressionType deriveExpressionType(ParseTree tree) { 21 | return deriveExpressionType(tree, Collections. emptyMap()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/RMQMessageListenerExecutionJMSException.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import jakarta.jms.JMSException; 9 | 10 | /** 11 | * Wraps an execution exception as a {@link JMSException}. 12 | */ 13 | public class RMQMessageListenerExecutionJMSException extends JMSException { 14 | 15 | /** Default version ID */ 16 | private static final long serialVersionUID = 1L; 17 | 18 | public RMQMessageListenerExecutionJMSException(String msg, Throwable x) { 19 | this(msg, null, x); 20 | } 21 | 22 | public RMQMessageListenerExecutionJMSException(Throwable x) { 23 | this(x.getMessage(), x); 24 | } 25 | 26 | private RMQMessageListenerExecutionJMSException(String msg, String errorCode, Throwable x) { 27 | super(msg, errorCode); 28 | this.initCause(x); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/SendingContext.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | 7 | package com.rabbitmq.jms.client; 8 | 9 | import jakarta.jms.Destination; 10 | import jakarta.jms.Message; 11 | 12 | /** 13 | * Context when sending message. 14 | * 15 | * @see com.rabbitmq.jms.admin.RMQConnectionFactory#setSendingContextConsumer(SendingContextConsumer) 16 | * @see SendingContextConsumer 17 | * @since 1.11.0 18 | */ 19 | public class SendingContext { 20 | 21 | private final Message message; 22 | 23 | private final Destination destination; 24 | 25 | public SendingContext(Destination destination, Message message) { 26 | this.destination = destination; 27 | this.message = message; 28 | } 29 | 30 | public Destination getDestination() { 31 | return destination; 32 | } 33 | 34 | public Message getMessage() { 35 | return message; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/Parser.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse; 3 | 4 | /** 5 | * A {@link Parser} produces a parse tree of nodes. 6 | *

7 | * Parsing never throws an exception but instead sets a success indicator and error message. 8 | *

9 | * 10 | * @param - type of node in the ParseTree produced 11 | */ 12 | public interface Parser { 13 | 14 | /** 15 | * This method returns the parse tree. It is idempotent. 16 | * 17 | * @return a {@link ParseTree ParseTree<Node>} capturing the result of a complete parse attempt. 18 | */ 19 | ParseTree parse(); 20 | 21 | /** 22 | * This call is idempotent. 23 | * @return true if the parse completed successfully, otherwise false, in which case the 24 | * termination message is set. 25 | */ 26 | boolean parseOk(); 27 | 28 | /** 29 | * This call is idempotent. 30 | * @return a string with a reason for termination. Only valid if {@link #parseOk()} returns false. 31 | */ 32 | String getErrorMessage(); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/ReplyToStrategy.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2016-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import jakarta.jms.JMSException; 9 | import jakarta.jms.Message; 10 | import java.io.Serializable; 11 | 12 | /** 13 | * Interface to provide a pluggable mechanism for dealing with messages received with a reply-to 14 | * queue specified. Implementations of this interface should update the message's JMSReplyTo 15 | * property directly. 16 | * 17 | * @since 2.9.0 18 | */ 19 | public interface ReplyToStrategy extends Serializable { 20 | 21 | String DIRECT_REPLY_TO = "amq.rabbitmq.reply-to"; 22 | 23 | /** 24 | * Handles the reply to on a received message. 25 | * 26 | * @param message The RMQMessage that has been received. 27 | * @param replyTo The reply to queue value received. 28 | * @throws JMSException if there's an issue updating the RMQMessage 29 | */ 30 | void handleReplyTo(Message message, String replyTo) throws JMSException; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/Compiler.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse; 3 | 4 | /** 5 | * A {@link Compiler} produces compiled code. Compiled code is produced as a {@link String}. 6 | *

7 | * Compiling never throws an exception, but instead returns prematurely. 8 | * The {@link #compileOk()} method indicates success or failure, and {@link #getErrorMessage()} 9 | * describes the error if {@link #compileOk()} returns false. 10 | *

11 | */ 12 | public interface Compiler { 13 | 14 | /** 15 | * Return the compiled code. This call is idempotent. 16 | * @return the compiled code 17 | */ 18 | String compile(); 19 | 20 | /** 21 | * Returns true if the compiled code is complete and compilation successful, false otherwise. 22 | * This call is idempotent. 23 | * @return true if the compiled code is complete and successful, false otherwise. 24 | */ 25 | boolean compileOk(); 26 | 27 | /** 28 | * This call is idempotent. 29 | * @return a message indicating the error during the compilation if there was one. 30 | */ 31 | String getErrorMessage(); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/sql/SqlExpressionType.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse.sql; 3 | 4 | /** 5 | * The types of the subexpressions in an SQL (selector) expression. 6 | * 7 | */ 8 | public enum SqlExpressionType { 9 | NOT_SET, // type not determined 10 | BOOL, // top level expression is a logical one 11 | STRING, // valid in equality comparisons 12 | ARITH, // all floats, hex's, integers have arithmetic type 13 | ANY, // unknown identifier means we cannot assign a type, though it may be valid when evaluated 14 | LIST, // only occurs in IN expressions, and must be a list of strings 15 | INVALID // a type error has occurred, and the expression cannot be typed properly (these propagate upward in a parse tree) 16 | ; 17 | 18 | static boolean isArith(SqlExpressionType expType) { 19 | return expType == ANY || expType == ARITH; 20 | } 21 | 22 | static boolean isString(SqlExpressionType expType) { 23 | return expType == ANY || expType == STRING; 24 | } 25 | 26 | static boolean isBool(SqlExpressionType expType) { 27 | return expType == ANY || expType == BOOL; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/PublisherConfirmContext.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2019-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import jakarta.jms.Message; 9 | 10 | /** 11 | * Information an outbound message being confirmed. 12 | * 13 | * @see ConfirmListener 14 | * @since 1.13.0 15 | */ 16 | public class PublisherConfirmContext { 17 | 18 | private final Message message; 19 | private final boolean ack; 20 | 21 | PublisherConfirmContext(Message message, boolean ack) { 22 | this.message = message; 23 | this.ack = ack; 24 | } 25 | 26 | /** 27 | * The message being confirmed. 28 | * 29 | * @return the confirmed message 30 | */ 31 | public Message getMessage() { 32 | return message; 33 | } 34 | 35 | /** 36 | * Whether the message is confirmed or nack-ed (considered lost). 37 | * 38 | * @return true if confirmed, false if nack-ed 39 | */ 40 | public boolean isAck() { 41 | return ack; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/AbstractITTopicSSL.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import jakarta.jms.TopicConnection; 9 | import jakarta.jms.TopicConnectionFactory; 10 | 11 | import org.junit.jupiter.api.AfterEach; 12 | import org.junit.jupiter.api.BeforeEach; 13 | 14 | public abstract class AbstractITTopicSSL { 15 | protected TopicConnectionFactory connFactory; 16 | protected TopicConnection topicConn; 17 | 18 | @BeforeEach 19 | public void beforeTests() throws Exception { 20 | this.connFactory = 21 | (TopicConnectionFactory) AbstractTestConnectionFactory.getTestConnectionFactory(true, 0) 22 | .getConnectionFactory(); 23 | this.topicConn = connFactory.createTopicConnection(); 24 | } 25 | 26 | @AfterEach 27 | public void afterTests() throws Exception { 28 | if (topicConn != null) 29 | topicConn.close(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/Multiples.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse; 3 | 4 | public interface Multiples { 5 | static class Pair { 6 | private final L l; 7 | private final R r; 8 | public Pair(L l, R r) { this.l = l ; this.r = r; } 9 | public L left() { return this.l; } 10 | public R right() { return this.r; } 11 | } 12 | static class Triple { 13 | private final Pair> p; 14 | public Triple(F f, S s, T t) { this.p=new Pair>(f, new Pair(s, t)); } 15 | public F first() { return this.p.left(); } 16 | public S second() { return this.p.right().left(); } 17 | public T third() { return this.p.right().right(); } 18 | } 19 | static class List { 20 | private final Pair> n; 21 | public List() { this.n = null; } 22 | public List(E e, List t) { this.n=new Pair>(e, t); } 23 | public boolean isEmpty() { return (this.n == null); } 24 | public E head() { return this.n.left(); } 25 | public List tail() { return this.n.right(); } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ## Changes Between 1.5.x and 1.6.0 2 | 3 | See https://github.com/rabbitmq/rabbitmq-jms-client/releases/tag/v1.6.0. 4 | 5 | ## Changes Between 1.4.7 and 1.5.0 6 | 7 | `1.5.0` is a maintainence release that includes a fix 8 | for `CVE-2016-6194`. 9 | 10 | ### Limited ObjectMessage Deserialization 11 | 12 | Classes that can be deserialized from `javax.jms.ObjectMessage` 13 | now can be limited via a package prefix white list. 14 | There are two ways to do it: 15 | 16 | * Via the `com.rabbitmq.jms.TrustedPackagesPrefixes` JVM property which accepts 17 | a comma separated list of prefixes, for example, `java,com.rabbitmq,com.mycompany` 18 | * Using `RMQConnectionFactory#setTrustedPackages`, `RMQConnection#setTrustedPackages`, or `RMQSession#setTrustedPackages` 19 | which accept lists of package prefixes 20 | 21 | All options take a list of package name prefixes, e.g. `java` will make all classes 22 | in `java.lang`, `java.util`, and other packages starting with `java` trusted. 23 | Deserialization attempt for untrusted classes will throw an exception. 24 | 25 | GH issue: [rabbitmq-jms-client#3](https://github.com/rabbitmq/rabbitmq-jms-client/issues/3). 26 | 27 | This fixes `CVE-2016-6194` (note: attacker must be authenticated 28 | with RabbitMQ in order to carry out the attack). 29 | 30 | 31 | ## RabbitMQ Java Client Dependency Update 32 | 33 | This client now depends on RabbitMQ Java client `3.6.3`. 34 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/Evaluator.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse; 3 | 4 | import java.util.Map; 5 | 6 | /** 7 | * An {@link Evaluator} will evaluate an expression. 8 | *

9 | * An evaluator takes a set of values (the environment) and evaluates an expression in it, returning a Result. 10 | * For example, it might evaluate a (previously given) boolean expression given a set of values assigned to variables. 11 | */ 12 | public interface Evaluator { 13 | 14 | /** 15 | * Evaluates the (given) expression with the environment given. 16 | * This call should not fail, provided {@link #evaluatorOk()} is true. 17 | * @param env - the values of the variables used when evaluating the expression 18 | * @return the evaluated result: true or false 19 | */ 20 | boolean evaluate(Map env); 21 | 22 | /** 23 | * This call is idempotent. 24 | * @return true if the evaluator can run cleanly; false otherwise 25 | */ 26 | boolean evaluatorOk(); 27 | 28 | /** 29 | * This call is idempotent. 30 | * @return null if evaluatorOk() is false; otherwise an error message denoting the type of failure to initialise. 31 | */ 32 | String getErrorMessage(); 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/jms/client/SessionParamsTest.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2016-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertNull; 9 | import static org.junit.jupiter.api.Assertions.assertSame; 10 | 11 | import org.junit.jupiter.api.Test; 12 | 13 | public class SessionParamsTest { 14 | 15 | @Test 16 | void createNoExplicitReplyToStrategy() { 17 | SessionParams params = new SessionParams(); 18 | 19 | assertSame(DefaultReplyToStrategy.INSTANCE, params.getReplyToStrategy()); 20 | } 21 | 22 | @Test 23 | void createNullReplyToStrategy() { 24 | SessionParams params = new SessionParams(); 25 | params.setReplyToStrategy(null); 26 | 27 | assertNull(params.getReplyToStrategy()); 28 | } 29 | 30 | @Test 31 | void createExplicitlySetReplyToStrategy() { 32 | SessionParams params = new SessionParams(); 33 | params.setReplyToStrategy(HandleAnyReplyToStrategy.INSTANCE); 34 | 35 | assertSame(HandleAnyReplyToStrategy.INSTANCE, params.getReplyToStrategy()); 36 | } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/jms/client/ConnectionParamsTest.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2016-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertNull; 9 | import static org.junit.jupiter.api.Assertions.assertSame; 10 | 11 | import org.junit.jupiter.api.Test; 12 | 13 | public class ConnectionParamsTest { 14 | 15 | @Test 16 | void createNoExplicitReplyToStrategy() { 17 | ConnectionParams params = new ConnectionParams(); 18 | 19 | assertSame(DefaultReplyToStrategy.INSTANCE, params.getReplyToStrategy()); 20 | } 21 | 22 | @Test 23 | void createNullReplyToStrategy() { 24 | ConnectionParams params = new ConnectionParams(); 25 | params.setReplyToStrategy(null); 26 | 27 | assertNull(params.getReplyToStrategy()); 28 | } 29 | 30 | @Test 31 | void createExplicitlySetReplyToStrategy() { 32 | ConnectionParams params = new ConnectionParams(); 33 | params.setReplyToStrategy(HandleAnyReplyToStrategy.INSTANCE); 34 | 35 | assertSame(HandleAnyReplyToStrategy.INSTANCE, params.getReplyToStrategy()); 36 | } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/Subscriptions.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | 7 | package com.rabbitmq.jms.client; 8 | 9 | import java.util.Map; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | class Subscriptions { 13 | 14 | private final Map nonDurableSubscriptions = new ConcurrentHashMap<>(); 15 | private final Map durableSubscriptions = new ConcurrentHashMap<>(); 16 | 17 | Subscription register(String name, String queue, boolean durable, boolean shared, String selector, 18 | boolean noLocal) { 19 | Map subscriptions = 20 | durable ? this.durableSubscriptions : this.nonDurableSubscriptions; 21 | return subscriptions.computeIfAbsent(name, 22 | n -> new Subscription(this, name, queue, durable, shared, selector, noLocal)); 23 | } 24 | 25 | Subscription get(boolean durable, String name) { 26 | return durable ? durableSubscriptions.get(name) : nonDurableSubscriptions.get(name); 27 | } 28 | 29 | Subscription remove(boolean durable, String name) { 30 | return durable ? this.durableSubscriptions.remove(name) 31 | : this.nonDurableSubscriptions.remove(name); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/util/AbortedException.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.util; 3 | 4 | import jakarta.jms.Connection; 5 | 6 | /** 7 | * Internal exception to indicate that waiting API call has been cancelled by other API action -- e.g. {@link Connection#close()}. 8 | */ 9 | public class AbortedException extends Exception { 10 | 11 | private static final long serialVersionUID = 1012573992638419310L; 12 | 13 | /** 14 | * Constructs a new exception with no message. 15 | */ 16 | public AbortedException() { 17 | } 18 | 19 | /** 20 | * Constructs a new exception with the specified detail message. 21 | * @param message the detail message. 22 | */ 23 | public AbortedException(String message) { 24 | super(message); 25 | } 26 | 27 | /** 28 | * Constructs a new exception with the specified Throwable as cause. 29 | * @param cause the cause of this exception. 30 | */ 31 | public AbortedException(Throwable cause) { 32 | super(cause); 33 | } 34 | 35 | /** 36 | * Constructs a new exception with the specified detail message and Throwable as cause. 37 | * @param message the detail message. 38 | * @param cause the cause of this exception. 39 | */ 40 | public AbortedException(String message, Throwable cause) { 41 | super(message, cause); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/test-supported-java-versions-3.x.yml: -------------------------------------------------------------------------------- 1 | name: Test against supported Java versions (3.x) 2 | 3 | on: 4 | schedule: 5 | - cron: '0 4 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-24.04 11 | strategy: 12 | matrix: 13 | distribution: [ 'temurin' ] 14 | version: [ '11', '17', '21', '25', '26-ea' ] 15 | include: 16 | - distribution: 'semeru' 17 | version: '17' 18 | name: Test against Java ${{ matrix.distribution }} ${{ matrix.version }} 19 | steps: 20 | - uses: actions/checkout@v6 21 | - name: Checkout tls-gen 22 | uses: actions/checkout@v6 23 | with: 24 | repository: rabbitmq/tls-gen 25 | path: './tls-gen' 26 | - name: Set up JDK 27 | uses: actions/setup-java@v5 28 | with: 29 | distribution: ${{ matrix.distribution }} 30 | java-version: ${{ matrix.version }} 31 | cache: 'maven' 32 | - name: Start RabbitMQ application 33 | run: ci/start-broker.sh 34 | - name: Display Java version 35 | run: ./mvnw --version 36 | - name: Test 37 | run: | 38 | ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq \ 39 | -Dtest-broker.A.nodename=rabbit@$(hostname) \ 40 | -Dtest-tls-certs.dir=tls-gen/basic \ 41 | --no-transfer-progress \ 42 | -Dnet.bytebuddy.experimental=true 43 | - name: Stop broker 44 | run: docker stop rabbitmq && docker rm rabbitmq 45 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | version: 2 4 | updates: 5 | - package-ecosystem: "maven" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | open-pull-requests-limit: 20 10 | target-branch: "main" 11 | ignore: 12 | - dependency-name: "ch.qos.logback:logback-classic" 13 | versions: [ "[1.3,)" ] 14 | - dependency-name: "jakarta.jms:jakarta.jms-api" 15 | versions: [ "[3.0.0,)" ] 16 | - dependency-name: "org.junit.jupiter:*" 17 | versions: [ "[6.0,)" ] 18 | - package-ecosystem: "maven" 19 | directory: "/" 20 | schedule: 21 | interval: "daily" 22 | open-pull-requests-limit: 20 23 | target-branch: "2.x.x-stable" 24 | ignore: 25 | - dependency-name: "ch.qos.logback:logback-classic" 26 | versions: [ "[1.3,)" ] 27 | - dependency-name: "org.springframework:spring-jms" 28 | versions: [ "[5.3,)" ] 29 | - dependency-name: "org.springframework:spring-context" 30 | versions: [ "[5.3,)" ] 31 | - dependency-name: "org.junit.jupiter:*" 32 | versions: [ "[6.0,)" ] 33 | - package-ecosystem: "github-actions" 34 | directory: "/" 35 | schedule: 36 | interval: "daily" 37 | target-branch: "main" 38 | - package-ecosystem: "github-actions" 39 | directory: "/" 40 | schedule: 41 | interval: "daily" 42 | target-branch: "2.x.x-stable" 43 | -------------------------------------------------------------------------------- /.github/workflows/test-supported-java-versions-2.x.yml: -------------------------------------------------------------------------------- 1 | name: Test against supported Java versions (2.x) 2 | 3 | on: 4 | schedule: 5 | - cron: '0 4 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-24.04 11 | strategy: 12 | matrix: 13 | distribution: [ 'temurin' ] 14 | version: [ '8', '11', '17', '21', '25', '26-ea' ] 15 | include: 16 | - distribution: 'semeru' 17 | version: '17' 18 | name: Test against Java ${{ matrix.distribution }} ${{ matrix.version }} 19 | steps: 20 | - uses: actions/checkout@v6 21 | with: 22 | ref: 2.x.x-stable 23 | - name: Checkout tls-gen 24 | uses: actions/checkout@v6 25 | with: 26 | repository: rabbitmq/tls-gen 27 | path: './tls-gen' 28 | - name: Set up JDK 29 | uses: actions/setup-java@v5 30 | with: 31 | distribution: ${{ matrix.distribution }} 32 | java-version: ${{ matrix.version }} 33 | cache: 'maven' 34 | - name: Start RabbitMQ application 35 | run: ci/start-broker.sh 36 | - name: Display Java version 37 | run: ./mvnw --version 38 | - name: Test 39 | run: | 40 | ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq \ 41 | -Dtest-broker.A.nodename=rabbit@$(hostname) \ 42 | -Dtest-tls-certs.dir=tls-gen/basic \ 43 | --no-transfer-progress \ 44 | -Dnet.bytebuddy.experimental=true 45 | - name: Stop broker 46 | run: docker stop rabbitmq && docker rm rabbitmq 47 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/sql/SqlTreeNode.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse.sql; 3 | 4 | /** 5 | * An {@link SqlParseTree} has a node at each point of the tree. 6 | * A Node has a type ({@link SqlTreeType}) which determines its degree and a value ({@link SqlToken}) which refines the operation this denotes. 7 | */ 8 | class SqlTreeNode { 9 | 10 | private final SqlTreeType treeType; 11 | private final SqlToken value; // null value means children 12 | 13 | /** type and value of expression represented by this node (if known); initially */ 14 | private SqlExpressionValue expValue = new SqlExpressionValue(); 15 | 16 | SqlTreeNode(SqlTreeType treeType, SqlToken value) { 17 | this.treeType = treeType; 18 | this.value = value; 19 | } 20 | 21 | SqlTreeNode(SqlTreeType treeType) { 22 | this(treeType, null); 23 | } 24 | 25 | // getter and setter for expression value - used by TypeSetter and Evaluator Visitors 26 | void setExpValue(SqlExpressionValue expValue) { this.expValue = expValue; } 27 | SqlExpressionValue getExpValue() { return this.expValue; } 28 | 29 | // getters for node value and type 30 | SqlToken value() { return this.value; } 31 | SqlTreeType treeType() { return this.treeType; } 32 | 33 | @Override 34 | public String toString() { 35 | return "SqlTreeNode [treeType=" + treeType + ", value=" + value + ", expValue=" + expValue + "]"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/test-alphas.yml: -------------------------------------------------------------------------------- 1 | name: Test against RabbitMQ alphas 2 | 3 | on: 4 | schedule: 5 | - cron: '0 4 * * *' 6 | push: 7 | branches: 8 | - main 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-24.04 14 | strategy: 15 | matrix: 16 | include: 17 | - rabbitmq-image: "pivotalrabbitmq/rabbitmq:v4.2.x-otp27" 18 | delayed-message-exchange-plugin-version: "4.2.0" 19 | - rabbitmq-image: "pivotalrabbitmq/rabbitmq:main-otp27" 20 | delayed-message-exchange-plugin-version: "4.2.0" 21 | name: Test against ${{ matrix.rabbitmq-image }} 22 | steps: 23 | - uses: actions/checkout@v6 24 | - name: Checkout tls-gen 25 | uses: actions/checkout@v6 26 | with: 27 | repository: rabbitmq/tls-gen 28 | path: './tls-gen' 29 | - name: Set up JDK 30 | uses: actions/setup-java@v5 31 | with: 32 | distribution: 'zulu' 33 | java-version: '25' 34 | cache: 'maven' 35 | - name: Start RabbitMQ application 36 | run: ci/start-broker.sh 37 | env: 38 | RABBITMQ_IMAGE: ${{ matrix.rabbitmq-image }} 39 | DELAYED_MESSAGE_EXCHANGE_PLUGIN_VERSION: ${{ matrix.delayed-message-exchange-plugin-version }} 40 | - name: Test 41 | run: | 42 | ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq \ 43 | -Dtest-broker.A.nodename=rabbit@$(hostname) \ 44 | -Dtest-tls-certs.dir=tls-gen/basic \ 45 | --no-transfer-progress 46 | - name: Stop broker 47 | run: docker stop rabbitmq && docker rm rabbitmq 48 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/sql/SqlCompiler.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse.sql; 3 | 4 | import static com.rabbitmq.jms.parse.ParseTreeTraverser.traverse; 5 | 6 | import com.rabbitmq.jms.parse.Compiler; 7 | 8 | public class SqlCompiler implements Compiler { 9 | 10 | private final String compiledCode; 11 | private final boolean compileOk; 12 | private final String errorMessage; 13 | 14 | public SqlCompiler(SqlEvaluator eval) { 15 | if (eval.evaluatorOk()) { 16 | SqlParseTree parseTree = eval.typedParseTree(); 17 | SqlCompilerVisitor compilerVisitor = new SqlCompilerVisitor(); 18 | if (this.compileOk = traverse(parseTree, compilerVisitor)) { 19 | this.compiledCode = compilerVisitor.extractCode() + "."; 20 | this.errorMessage = null; 21 | } else { 22 | this.compiledCode = null; 23 | this.errorMessage = "Could not compile parsed tree "+ parseTree.formattedTree(); 24 | } 25 | } else { 26 | this.compileOk = false; 27 | this.compiledCode = null; 28 | this.errorMessage = eval.getErrorMessage(); 29 | } 30 | } 31 | 32 | @Override 33 | public String compile() { 34 | return this.compiledCode; 35 | } 36 | 37 | @Override 38 | public boolean compileOk() { 39 | return this.compileOk; 40 | } 41 | 42 | @Override 43 | public String getErrorMessage() { 44 | return this.errorMessage; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/jms/client/UtilsTest.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | 7 | package com.rabbitmq.jms.client; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | import org.junit.jupiter.params.ParameterizedTest; 12 | import org.junit.jupiter.params.provider.CsvSource; 13 | 14 | public class UtilsTest { 15 | 16 | @CsvSource({ 17 | "valid-subscription-name,true", 18 | "valid_subscription_name,true", 19 | "valid.subscription.name,true", 20 | "yet-another_valid.subscription.name,true", 21 | "invalid?subscription!name,false", 22 | "invalid(subscription)name,false", 23 | "invalid(subscription)name,false", 24 | "very.loooooooooooooooooooooooooooooooooooooooooooooooooooooooo" 25 | + "ooooooooooooooooooooooooooooooooooooooooooooong.subscription.name,true", 26 | "too.loooooooooooooooooooooooooooooooooooooooooooooooooooooooo" 27 | + "oooooooooooooooooooooooooooooooooooooooooooooooooooong.subscription.name,false", 28 | }) 29 | @ParameterizedTest 30 | void subscriptionNameValidation(String subscriptionName, boolean valid) { 31 | // https://jakarta.ee/specifications/messaging/3.1/jakarta-messaging-spec-3.1.html#subscription-name-characters-and-length 32 | assertThat(Utils.SUBSCRIPTION_NAME_PREDICATE.test(subscriptionName)).isEqualTo(valid); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/ParseTree.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | /** 3 | * Parse trees capture a structured view of a parsed token stream. 4 | * They encapsulate the result of a parse() operation. 5 | *

6 | * 7 | *

8 | *

9 | * Traversal algorithms will assume that there are no loops in the tree (no child can be its own ancestor). 10 | *

11 | */ 12 | package com.rabbitmq.jms.parse; 13 | 14 | /** 15 | * A non-empty tree of {@link Node}s which can be traversed. 16 | *

17 | * The tree is a {@link Node} and a(n array of) children (if there are no children this must be a zero-length array and must not be null). 18 | * Each child in the array is a {@link ParseTree ParseTree<Node>}. 19 | *

20 | * @param - the type of nodes in the tree, a {@link Node} is attached to the root of each subtree. 21 | */ 22 | public interface ParseTree { 23 | 24 | /** 25 | * @return the node at the root of the tree. 26 | */ 27 | Node getNode(); 28 | 29 | /** 30 | * Convenience method to avoid creating children prematurely. Must be the same as getChildren().length. 31 | * @return the number of children of the root. 32 | */ 33 | int getNumberOfChildren(); 34 | 35 | 36 | /** 37 | * Get the immediate children of the root of the tree. 38 | * @return an array of children. 39 | */ 40 | ParseTree[] getChildren(); 41 | 42 | /** 43 | * Get the nodes of the immediate children of the root of the tree. 44 | * @return an array of {@link Node}s. 45 | */ 46 | Node[] getChildNodes(); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | RabbitMQ projects use pull requests to discuss, collaborate on and accept code contributions. 4 | Pull requests is the primary place of discussing code changes. 5 | 6 | ## How to Contribute 7 | 8 | The process is fairly standard: 9 | 10 | * Fork the repository or repositories you plan on contributing to 11 | * Clone [RabbitMQ umbrella repository](https://github.com/rabbitmq/rabbitmq-public-umbrella) 12 | * `cd umbrella`, `make co` 13 | * Create a branch with a descriptive name in the relevant repositories 14 | * Make your changes, run tests, commit with a [descriptive message](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), push to your fork 15 | * Submit pull requests with an explanation what has been changed and **why** 16 | * Submit a filled out and signed [Contributor Agreement](https://cla.pivotal.io) if needed (see below) 17 | * Be patient. We will get to your pull request eventually 18 | 19 | If what you are going to work on is a substantial change, please first ask the core team 20 | of their opinion on [RabbitMQ mailing list](https://groups.google.com/forum/#!forum/rabbitmq-users). 21 | 22 | 23 | ## Code of Conduct 24 | 25 | See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). 26 | 27 | 28 | ## Contributor Agreement 29 | 30 | If you want to contribute a non-trivial change, please submit a signed copy of our 31 | [Contributor Agreement](https://cla.pivotal.io) around the time 32 | you submit your pull request. This will make it much easier (in some cases, possible) 33 | for the RabbitMQ team at Pivotal to merge your contribution. 34 | 35 | 36 | ## Where to Ask Questions 37 | 38 | If something isn't clear, feel free to ask on our [mailing list](https://groups.google.com/forum/#!forum/rabbitmq-users). 39 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/AbstractITTopic.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import com.rabbitmq.jms.admin.RMQConnectionFactory; 9 | import jakarta.jms.TopicConnection; 10 | import jakarta.jms.TopicConnectionFactory; 11 | 12 | import org.junit.jupiter.api.AfterEach; 13 | import org.junit.jupiter.api.BeforeEach; 14 | 15 | public abstract class AbstractITTopic { 16 | protected TopicConnectionFactory connFactory; 17 | protected TopicConnection topicConn; 18 | 19 | @BeforeEach 20 | public void beforeTests() throws Exception { 21 | this.connFactory = 22 | (TopicConnectionFactory) AbstractTestConnectionFactory.getTestConnectionFactory() 23 | .getConnectionFactory(); 24 | customise((RMQConnectionFactory) connFactory); 25 | this.topicConn = connFactory.createTopicConnection(); 26 | } 27 | 28 | protected void customise(RMQConnectionFactory connectionFactory) { 29 | 30 | } 31 | 32 | @AfterEach 33 | public void afterTests() throws Exception { 34 | if (this.topicConn != null) 35 | this.topicConn.close(); 36 | } 37 | 38 | protected void reconnect() throws Exception { 39 | if (this.topicConn != null) 40 | this.topicConn.close(); 41 | this.topicConn = connFactory.createTopicConnection(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release JMS Client 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-24.04 9 | 10 | steps: 11 | - uses: actions/checkout@v6 12 | - name: Evaluate release information 13 | run: | 14 | source ./release-versions.txt 15 | echo "java_version=$JAVA_VERSION" >> $GITHUB_ENV 16 | - name: Set up JDK 17 | uses: actions/setup-java@v5 18 | with: 19 | distribution: 'temurin' 20 | java-version: ${{ env.java_version }} 21 | cache: 'maven' 22 | server-id: central 23 | server-username: MAVEN_USERNAME 24 | server-password: MAVEN_PASSWORD 25 | gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} 26 | gpg-passphrase: MAVEN_GPG_PASSPHRASE 27 | - name: Release JMS Client 28 | run: | 29 | git config user.name "rabbitmq-ci" 30 | git config user.email "rabbitmq-ci@users.noreply.github.com" 31 | ci/release-jms-client.sh 32 | env: 33 | MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }} 34 | MAVEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN }} 35 | MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 36 | - name: Set up JDK for documentation generation 37 | uses: actions/setup-java@v5 38 | with: 39 | distribution: 'temurin' 40 | java-version: '25' 41 | cache: 'maven' 42 | - name: Generate documentation 43 | run: make doc 44 | - name: Publish documentation 45 | run: | 46 | git config user.name "rabbitmq-ci" 47 | git config user.email "rabbitmq-ci@users.noreply.github.com" 48 | ci/publish-documentation-to-github-pages.sh 49 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/DefaultReplyToStrategy.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import com.rabbitmq.jms.admin.RMQDestination; 9 | import jakarta.jms.JMSException; 10 | import jakarta.jms.Message; 11 | 12 | /** 13 | * Default implementation of the reply-to strategy. 14 | * 15 | * This will ensure that any reply to queues using the 16 | * amq.rabbitmq.reply-to. are correctly created. 17 | * 18 | * @since 2.9.0 19 | */ 20 | public class DefaultReplyToStrategy implements ReplyToStrategy { 21 | 22 | private static final long serialVersionUID = -496756742546656456L; 23 | 24 | /** An instance of the strategy to avoid having to create multiple copies of the object. */ 25 | public static final DefaultReplyToStrategy INSTANCE = new DefaultReplyToStrategy(); 26 | 27 | /** 28 | * Handles the reply to on a received message. 29 | * 30 | * @param message The RMQMessage that has been received. 31 | * @param replyTo The reply to queue value received. 32 | * @throws JMSException if there's an issue updating the RMQMessage 33 | */ 34 | @Override 35 | public void handleReplyTo( 36 | final Message message, 37 | final String replyTo) throws JMSException { 38 | 39 | if (replyTo != null && replyTo.startsWith(DIRECT_REPLY_TO)) { 40 | message.setJMSReplyTo(new RMQDestination(DIRECT_REPLY_TO, "", replyTo, replyTo)); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/jms/util/TestUtil.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.util; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | import java.util.HashSet; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | import org.junit.jupiter.api.Test; 17 | 18 | public class TestUtil { 19 | 20 | private static final String PREFIX = "abcd-"; 21 | 22 | /** 23 | * Test generateUUID puts the prefix on. 24 | */ 25 | @Test 26 | public void generateUUIDPrefix() throws Exception { 27 | assertTrue(Util.generateUUID(PREFIX).startsWith(PREFIX), "Prefix '"+PREFIX+"' not added correctly"); 28 | assertTrue(Util.generateUUID(null).startsWith("null"), "Null case doesn't start with null"); // special case 29 | } 30 | 31 | private static final int REPEAT_COUNT = 1000; 32 | 33 | /** 34 | * Test generateUUID doesn't repeat quickly. 35 | */ 36 | @Test 37 | public void generateUUIDMultiple() throws Exception { 38 | List uuidList = new LinkedList(); 39 | 40 | for(int i=0; i Set setOf(List list) { return new HashSet(list); } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/SendingContextConsumer.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import jakarta.jms.JMSException; 9 | import java.util.Objects; 10 | import java.util.function.Consumer; 11 | 12 | /** 13 | * Callback before sending a message. 14 | * 15 | * @see com.rabbitmq.jms.admin.RMQConnectionFactory#setSendingContextConsumer(SendingContextConsumer) 16 | * @see SendingContext 17 | * @since 1.11.0 18 | */ 19 | @FunctionalInterface 20 | public interface SendingContextConsumer { 21 | 22 | /** 23 | * Called before sending a message. 24 | *

25 | * Can be used to customize the message or the destination 26 | * before the message is actually sent. 27 | * 28 | * @param sendingContext 29 | * @throws JMSException 30 | */ 31 | void accept(SendingContext sendingContext) throws JMSException; 32 | 33 | /** 34 | * Same semantics as {@link Consumer#andThen(Consumer)}. 35 | * 36 | * @param after the operation to perform after this operation 37 | * @return a composed {@code SendingContextConsumer} that performs in sequence this 38 | * operation followed by the {@code after} operation 39 | * @throws NullPointerException if {@code after} is null 40 | */ 41 | default SendingContextConsumer andThen(SendingContextConsumer after) { 42 | Objects.requireNonNull(after); 43 | return (SendingContext ctx) -> { 44 | accept(ctx); 45 | after.accept(ctx); 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/util/DiscardingObjectOutput.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.util; 3 | 4 | import java.io.IOException; 5 | import java.io.ObjectOutput; 6 | 7 | /** 8 | * Simple implementation of an {@link ObjectOutput} interface that accepts and discards all output. 9 | */ 10 | public class DiscardingObjectOutput implements ObjectOutput { 11 | 12 | public void writeBoolean(boolean paramBoolean) throws IOException { 13 | } 14 | 15 | public void writeByte(int paramInt) throws IOException { 16 | } 17 | 18 | public void writeShort(int paramInt) throws IOException { 19 | } 20 | 21 | public void writeChar(int paramInt) throws IOException { 22 | } 23 | 24 | public void writeInt(int paramInt) throws IOException { 25 | } 26 | 27 | public void writeLong(long paramLong) throws IOException { 28 | } 29 | 30 | public void writeFloat(float paramFloat) throws IOException { 31 | } 32 | 33 | public void writeDouble(double paramDouble) throws IOException { 34 | } 35 | 36 | public void writeBytes(String paramString) throws IOException { 37 | } 38 | 39 | public void writeChars(String paramString) throws IOException { 40 | } 41 | 42 | public void writeUTF(String paramString) throws IOException { 43 | } 44 | 45 | public void writeObject(Object paramObject) throws IOException { 46 | } 47 | 48 | public void write(int paramInt) throws IOException { 49 | } 50 | 51 | public void write(byte[] paramArrayOfByte) throws IOException { 52 | } 53 | 54 | public void write(byte[] paramArrayOfByte, int paramInt1, int paramInt2) throws IOException { 55 | } 56 | 57 | public void flush() throws IOException { 58 | } 59 | 60 | public void close() throws IOException { 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/ReceivingContextConsumer.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import jakarta.jms.JMSException; 9 | import java.util.Objects; 10 | import java.util.function.Consumer; 11 | 12 | /** 13 | * Callback before receiving a message. 14 | * 15 | * @see com.rabbitmq.jms.admin.RMQConnectionFactory#setSendingContextConsumer(SendingContextConsumer) 16 | * @see SendingContext 17 | * @since 1.11.0 18 | */ 19 | @FunctionalInterface 20 | public interface ReceivingContextConsumer { 21 | 22 | ReceivingContextConsumer NO_OP = ctx -> { 23 | }; 24 | 25 | /** 26 | * Called before receiving a message. 27 | *

28 | * Can be used to customize the message 29 | * before it is dispatched to application code. 30 | * 31 | * @param receivingContext 32 | * @throws JMSException 33 | */ 34 | void accept(ReceivingContext receivingContext) throws JMSException; 35 | 36 | /** 37 | * Same semantics as {@link Consumer#andThen(Consumer)}. 38 | * 39 | * @param after the operation to perform after this operation 40 | * @return a composed {@code ReceivingContextConsumer} that performs in sequence this 41 | * operation followed by the {@code after} operation 42 | * @throws NullPointerException if {@code after} is null 43 | */ 44 | default ReceivingContextConsumer andThen(ReceivingContextConsumer after) { 45 | Objects.requireNonNull(after); 46 | return (ReceivingContext ctx) -> { 47 | accept(ctx); 48 | after.accept(ctx); 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/AbstractAmqpITQueue.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import jakarta.jms.QueueConnection; 9 | import jakarta.jms.QueueConnectionFactory; 10 | 11 | import org.junit.jupiter.api.AfterEach; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import com.rabbitmq.jms.admin.RMQConnectionFactory; 14 | 15 | import com.rabbitmq.client.Channel; 16 | import com.rabbitmq.client.Connection; 17 | import com.rabbitmq.client.ConnectionFactory; 18 | 19 | public abstract class AbstractAmqpITQueue { 20 | protected QueueConnectionFactory connFactory; 21 | protected QueueConnection queueConn; 22 | 23 | private ConnectionFactory rabbitConnFactory = new ConnectionFactory(); 24 | protected Connection rabbitConn; 25 | protected Channel channel; 26 | 27 | @BeforeEach 28 | public void setUp() throws Exception { 29 | this.connFactory = (QueueConnectionFactory) AbstractTestConnectionFactory.getTestConnectionFactory() 30 | .getConnectionFactory(); 31 | customise((RMQConnectionFactory) connFactory); 32 | this.queueConn = connFactory.createQueueConnection(); 33 | 34 | this.rabbitConn = rabbitConnFactory.newConnection(); 35 | this.channel = rabbitConn.createChannel(); 36 | } 37 | 38 | protected void customise(RMQConnectionFactory connectionFactory) { 39 | 40 | } 41 | 42 | @AfterEach 43 | public void tearDown() throws Exception { 44 | if (this.queueConn != null) this.queueConn.close(); 45 | if (this.channel != null) this.channel.close(); 46 | if (this.rabbitConn != null) this.rabbitConn.close(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /ci/start-broker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.2} 4 | DELAYED_MESSAGE_EXCHANGE_PLUGIN_VERSION=${DELAYED_MESSAGE_EXCHANGE_PLUGIN_VERSION:-4.2.0} 5 | 6 | wait_for_message() { 7 | while ! docker logs "$1" | grep -q "$2"; 8 | do 9 | sleep 5 10 | echo "Waiting 5 seconds for $1 to start..." 11 | done 12 | } 13 | 14 | make -C "${PWD}"/tls-gen/basic 15 | 16 | rm -rf rabbitmq-configuration 17 | 18 | mkdir -p rabbitmq-configuration/tls 19 | cp -R "${PWD}"/tls-gen/basic/result/* rabbitmq-configuration/tls 20 | chmod o+r rabbitmq-configuration/tls/* 21 | chmod g+r rabbitmq-configuration/tls/* 22 | 23 | echo "[rabbitmq_jms_topic_exchange,rabbitmq_delayed_message_exchange]." > rabbitmq-configuration/enabled_plugins 24 | 25 | echo "loopback_users = none 26 | 27 | listeners.ssl.default = 5671 28 | 29 | ssl_options.cacertfile = /etc/rabbitmq/tls/ca_certificate.pem 30 | ssl_options.certfile = /etc/rabbitmq/tls/server_$(hostname)_certificate.pem 31 | ssl_options.keyfile = /etc/rabbitmq/tls/server_$(hostname)_key.pem 32 | ssl_options.verify = verify_peer 33 | ssl_options.fail_if_no_peer_cert = false" > rabbitmq-configuration/rabbitmq.conf 34 | 35 | echo "Running RabbitMQ ${RABBITMQ_IMAGE}" 36 | 37 | docker rm -f rabbitmq 2>/dev/null || echo "rabbitmq was not running" 38 | docker run -d --name rabbitmq \ 39 | --network host \ 40 | -v "${PWD}"/rabbitmq-configuration:/etc/rabbitmq \ 41 | -v "${PWD}"/ci/rabbitmq_delayed_message_exchange-"${DELAYED_MESSAGE_EXCHANGE_PLUGIN_VERSION}".ez:/opt/rabbitmq/plugins/rabbitmq_delayed_message_exchange-"${DELAYED_MESSAGE_EXCHANGE_PLUGIN_VERSION}".ez \ 42 | "${RABBITMQ_IMAGE}" 43 | 44 | wait_for_message rabbitmq "completed with" 45 | 46 | docker exec rabbitmq rabbitmqctl enable_feature_flag --opt-in khepri_db 47 | docker exec rabbitmq rabbitmq-plugins disable rabbitmq_delayed_message_exchange 48 | docker exec rabbitmq rabbitmq-plugins enable rabbitmq_delayed_message_exchange 49 | docker exec rabbitmq rabbitmq-diagnostics erlang_version 50 | docker exec rabbitmq rabbitmqctl version 51 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/DelayedTopicMessageIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import com.rabbitmq.TestUtils.SkipIfDelayedMessageExchangePluginNotActivated; 9 | import jakarta.jms.*; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | /** 15 | * Integration test 16 | */ 17 | @SkipIfDelayedMessageExchangePluginNotActivated 18 | public class DelayedTopicMessageIT extends AbstractITTopic { 19 | 20 | private static final String TOPIC_NAME = "delay.topic." + DelayedTopicMessageIT.class.getCanonicalName(); 21 | private static final String MESSAGE_TEXT_1 = "Hello " + DelayedTopicMessageIT.class.getName(); 22 | 23 | 24 | @Test 25 | public void testSendMessageToTopicWithDelayedPublisher() throws Exception { 26 | topicConn.start(); 27 | TopicSession topicSession = topicConn.createTopicSession(false, Session.DUPS_OK_ACKNOWLEDGE); 28 | Topic topic = topicSession.createTopic(TOPIC_NAME); 29 | TopicPublisher sender = topicSession.createPublisher(topic); 30 | sender.setDeliveryDelay(4000L); 31 | TopicSubscriber receiver = topicSession.createSubscriber(topic); 32 | 33 | // TODO probably ensure the topic is empty 34 | sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 35 | TextMessage message = topicSession.createTextMessage(MESSAGE_TEXT_1); 36 | sender.send(message); 37 | 38 | assertNull(receiver.receive(2000L)); 39 | TextMessage receivedMessage = (TextMessage)receiver.receive(); 40 | assertTrue(receivedMessage.getJMSDeliveryTime() > 0); 41 | assertEquals(MESSAGE_TEXT_1, receivedMessage.getText()); 42 | 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/HandleAnyReplyToStrategy.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import com.rabbitmq.jms.admin.RMQDestination; 9 | import jakarta.jms.JMSException; 10 | import jakarta.jms.Message; 11 | 12 | /** 13 | * Implementation of the reply to strategy that deals with any reply-to 14 | * value received and will use the default, i.e. "", exchange. 15 | * 16 | * If the reply-to starts with "amq.rabbitmq.reply-to", this will 17 | * correctly handle these types of temporary queues. 18 | * 19 | * @since 2.9.0 20 | */ 21 | public class HandleAnyReplyToStrategy implements ReplyToStrategy { 22 | 23 | private static final long serialVersionUID = -496756742546656456L; 24 | 25 | /** An instance of the strategy to avoid having to create multiple copies of the object. */ 26 | public static final HandleAnyReplyToStrategy INSTANCE = new HandleAnyReplyToStrategy(); 27 | 28 | /** 29 | * Handles the reply to on a received message. 30 | * 31 | * @param message The RMQMessage that has been received. 32 | * @param replyTo The reply to queue value received. 33 | * @throws JMSException if there's an issue updating the RMQMessage 34 | */ 35 | @Override 36 | public void handleReplyTo( 37 | final Message message, 38 | final String replyTo) throws JMSException { 39 | 40 | if (replyTo != null && !"".equals(replyTo)) { 41 | if (replyTo.startsWith(DIRECT_REPLY_TO)) { 42 | message.setJMSReplyTo(new RMQDestination(DIRECT_REPLY_TO, "", replyTo, replyTo)); 43 | } else { 44 | message.setJMSReplyTo(new RMQDestination(replyTo, "", replyTo, replyTo)); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/SanityCheck.java: -------------------------------------------------------------------------------- 1 | ///usr/bin/env jbang "$0" "$@" ; exit $? 2 | //DEPS com.rabbitmq.jms:rabbitmq-jms:${env.RABBITMQ_LIBRARY_VERSION} 3 | //DEPS org.slf4j:slf4j-simple:1.7.36 4 | 5 | import com.rabbitmq.jms.admin.RMQConnectionFactory; 6 | import jakarta.jms.DeliveryMode; 7 | import jakarta.jms.Message; 8 | import jakarta.jms.Queue; 9 | import jakarta.jms.QueueConnection; 10 | import jakarta.jms.QueueReceiver; 11 | import jakarta.jms.QueueSender; 12 | import jakarta.jms.QueueSession; 13 | import jakarta.jms.Session; 14 | import org.slf4j.LoggerFactory; 15 | 16 | public class SanityCheck { 17 | 18 | public static void main(String[] args) throws Exception { 19 | QueueConnection connection = null; 20 | try { 21 | connection = (QueueConnection) new RMQConnectionFactory().createConnection(); 22 | connection.start(); 23 | QueueSession queueSession = connection.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 24 | Queue queue = queueSession.createTemporaryQueue(); 25 | 26 | QueueSender queueSender = queueSession.createSender(queue); 27 | queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 28 | Message message = queueSession.createTextMessage("Hello"); 29 | queueSender.send(message); 30 | 31 | QueueReceiver queueReceiver = queueSession.createReceiver(queue); 32 | message = queueReceiver.receive(5000); 33 | if (message == null) throw new IllegalStateException("Didn't receive message in 5 seconds"); 34 | LoggerFactory.getLogger("rabbitmq") 35 | .info( 36 | "Test succeeded with JMS Client {}", 37 | RMQConnectionFactory.class.getPackage().getImplementationVersion()); 38 | System.exit(0); 39 | } catch (Exception e) { 40 | LoggerFactory.getLogger("rabbitmq") 41 | .info( 42 | "Test failed with JMS Client {}", 43 | RMQConnectionFactory.class.getPackage().getImplementationVersion(), 44 | e); 45 | if (connection != null) { 46 | connection.close(); 47 | } 48 | System.exit(1); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/SSLSimpleTopicMessageIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | import com.rabbitmq.TestUtils.SkipIfTlsNotActivated; 11 | import jakarta.jms.DeliveryMode; 12 | import jakarta.jms.Session; 13 | import jakarta.jms.TextMessage; 14 | import jakarta.jms.Topic; 15 | import jakarta.jms.TopicPublisher; 16 | import jakarta.jms.TopicSession; 17 | import jakarta.jms.TopicSubscriber; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | /** 22 | * Integration test 23 | */ 24 | @SkipIfTlsNotActivated 25 | public class SSLSimpleTopicMessageIT extends AbstractITTopicSSL { 26 | private static final String TOPIC_NAME = "test.topic." + SSLSimpleTopicMessageIT.class.getCanonicalName(); 27 | 28 | @Test 29 | public void testSendAndReceiveTextMessage() throws Exception { 30 | final String MESSAGE2 = "Hello " + SSLSimpleTopicMessageIT.class.getName(); 31 | topicConn.start(); 32 | TopicSession topicSession = topicConn.createTopicSession(false, Session.DUPS_OK_ACKNOWLEDGE); 33 | Topic topic = topicSession.createTopic(TOPIC_NAME); 34 | TopicPublisher sender = topicSession.createPublisher(topic); 35 | TopicSubscriber receiver1 = topicSession.createSubscriber(topic); 36 | TopicSubscriber receiver2 = topicSession.createSubscriber(topic); 37 | 38 | sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 39 | TextMessage message = topicSession.createTextMessage(MESSAGE2); 40 | sender.send(message); 41 | 42 | assertEquals(MESSAGE2, ((TextMessage) receiver1.receive()).getText()); 43 | assertEquals(MESSAGE2, ((TextMessage) receiver2.receive()).getText()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/RMQNullMessage.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.ObjectInput; 12 | import java.io.ObjectOutput; 13 | 14 | import jakarta.jms.JMSException; 15 | import jakarta.jms.Message; 16 | 17 | 18 | class RMQNullMessage extends RMQMessage { 19 | 20 | // there is no body 21 | 22 | @Override 23 | protected void clearBodyInternal() throws JMSException { 24 | // no-op 25 | } 26 | 27 | @Override 28 | protected void writeBody(ObjectOutput out, ByteArrayOutputStream bout) throws IOException { 29 | // no-op 30 | } 31 | 32 | @Override 33 | protected void writeAmqpBody(ByteArrayOutputStream out) throws IOException { 34 | // no-op 35 | } 36 | 37 | @Override 38 | protected void readBody(ObjectInput inputStream, ByteArrayInputStream bin) throws IOException, 39 | ClassNotFoundException { 40 | // no-op 41 | } 42 | 43 | @Override 44 | protected void readAmqpBody(byte[] barr) { 45 | // no-op 46 | } 47 | 48 | public static final RMQMessage recreate(Message msg) throws JMSException { 49 | RMQNullMessage rmqNMsg = new RMQNullMessage(); 50 | RMQMessage.copyAttributes(rmqNMsg, msg); 51 | return rmqNMsg; 52 | } 53 | 54 | @Override 55 | public boolean isBodyAssignableTo(Class c) { 56 | return true; 57 | } 58 | 59 | @Override 60 | protected T doGetBody(Class c) { 61 | return null; 62 | } 63 | 64 | @Override 65 | public boolean isAmqpWritable() { 66 | return false; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/Visitor.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse; 3 | 4 | /** 5 | * A visitor is guided by a {@link ParseTreeTraverser} to each {@link Node} of a tree. 6 | *

7 | * It is intended that {@link Visitor#visitBefore(Object, Object[])} and {@link Visitor#visitAfter(Object, Object[])} are called for each node in the tree. 8 | * The order of traversal is both pre- and post-order: {@link #visitBefore(Object, Object[])} is called on a parent before the children are called, 9 | * and {@link Visitor#visitAfter(Object, Object[])} is called after the children are called. 10 | *

11 | *

12 | * The returned boolean is interpreted by the caller and its meaning is not part of the {@link Visitor} contract. 13 | *

14 | */ 15 | public interface Visitor { 16 | 17 | 18 | /** 19 | * Visit a place in the tree: the Node for this place in the tree, and an array of {@link Node}s of the immediate children, are passed as parameters. 20 | * @param parent - the node (value) of the parent at this point in the tree; can be updated. 21 | * @param children - array of the nodes of the children of this parent (can be zero-length array; must not be null). 22 | * @return true or false - interpreted by the caller (typically a tree traversal algorithm). 23 | */ 24 | boolean visitBefore(Node parent, Node[] children); 25 | 26 | /** 27 | * Visit a place in the tree: the Node for this place in the tree, and an array of {@link Node}s of the immediate children, are passed as parameters. 28 | * @param parent - the node (value) of the parent at this point in the tree; can be updated. 29 | * @param children - array of the nodes of the children of this parent (can be zero-length array; must not be null). 30 | * @return true or false - interpreted by the caller (typically a tree traversal algorithm). 31 | */ 32 | boolean visitAfter (Node parent, Node[] children); 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/DelayedAMQPQueueMessageIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import com.rabbitmq.TestUtils.SkipIfDelayedMessageExchangePluginNotActivated; 9 | import com.rabbitmq.jms.admin.RMQDestination; 10 | import jakarta.jms.JMSConsumer; 11 | import jakarta.jms.JMSContext; 12 | import jakarta.jms.JMSProducer; 13 | import org.junit.jupiter.api.AfterEach; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Test; 16 | 17 | import static org.junit.jupiter.api.Assertions.assertNotNull; 18 | import static org.junit.jupiter.api.Assertions.assertNull; 19 | 20 | /** 21 | * Integration test 22 | */ 23 | @SkipIfDelayedMessageExchangePluginNotActivated 24 | public class DelayedAMQPQueueMessageIT extends AbstractAmqpITQueue { 25 | 26 | String queueName = "DelayedAMQPQueueMessageIT"; 27 | RMQDestination destination; 28 | 29 | @BeforeEach 30 | void init() { 31 | destination = new RMQDestination(queueName, true, false); 32 | } 33 | 34 | @AfterEach 35 | void dispose() throws Exception { 36 | this.channel.queueDelete(queueName); 37 | } 38 | 39 | @Test 40 | void sendMessageToAMQPQueue() { 41 | 42 | try (JMSContext context = this.connFactory.createContext()) { 43 | JMSProducer producer = context.createProducer(); 44 | producer.setDeliveryDelay(4000L); 45 | producer.send(destination, "test message"); 46 | } 47 | 48 | try (JMSContext context = this.connFactory.createContext(JMSContext.AUTO_ACKNOWLEDGE)) { 49 | JMSConsumer consumer = context.createConsumer(destination); 50 | 51 | assertNull(consumer.receive(2000L)); 52 | assertNotNull(consumer.receive(3000L)); 53 | } 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/jms/parse/sql/SqlCompilerTest.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.parse.sql; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNull; 10 | import static org.junit.jupiter.api.Assertions.fail; 11 | 12 | import java.util.Arrays; 13 | import java.util.Collections; 14 | 15 | import org.junit.jupiter.api.Test; 16 | 17 | public class SqlCompilerTest { 18 | 19 | @Test 20 | public void simpleCompiles() { 21 | assertCompile( "nothing is null" 22 | , "{'is_null',{'ident',<<\"nothing\">>}}." ); 23 | 24 | assertCompile( "nothing IS NULL And JMSPrefix > 12-4" 25 | , "{'and'"+ 26 | ",{'is_null',{'ident',<<\"nothing\">>}}"+ 27 | ",{'>'"+ 28 | ",{'ident',<<\"JMSPrefix\">>}"+ 29 | ",{'-',12,4}}}." ); 30 | } 31 | 32 | private void assertCompile(String inStr, String outStr) { 33 | SqlTokenStream stream = new SqlTokenStream(inStr); 34 | assertEquals("", stream.getResidue(), "Residue not empty"); 35 | 36 | SqlParser sp = new SqlParser(stream); 37 | SqlParseTree pt = sp.parse(); 38 | 39 | if (!sp.parseOk()) { 40 | fail(sp.getErrorMessage()); 41 | } 42 | 43 | SqlCompiler compiled = new SqlCompiler(new SqlEvaluator(sp, Collections. emptyMap())); 44 | if (!compiled.compileOk()) { 45 | fail("Did not compile, tree=" + Arrays.toString(pt.formattedTree())); 46 | } 47 | assertNull(compiled.getErrorMessage(), "Error message generated!"); 48 | assertEquals(outStr, compiled.compile(), "Compiled code doesn't match"); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/admin/DefaultNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.jms.admin; 2 | 3 | /** 4 | * Default implementation for {@link NamingStrategy}. 5 | */ 6 | final class DefaultNamingStrategy implements NamingStrategy { 7 | 8 | private final String topicExchangeName = "jms.durable.topic"; 9 | 10 | private final String temporaryTopicExchangeName = "jms.temp.topic"; 11 | 12 | private final String queueExchangeName = "jms.durable.queues"; 13 | 14 | private final String temporaryQueueExchangeName = "jms.temp.queues"; 15 | 16 | private final String topicSubscriberQueuePrefix = "jms-cons-"; 17 | 18 | private final String durableSubscriberTopicSelectorExchangePrefix = "jms-dutop-slx-"; 19 | 20 | private final String nonDurableSubscriberTopicSelectorExchangePrefix = "jms-ndtop-slx-"; 21 | 22 | private final String temporaryQueuePrefix = "jms-temp-queue-"; 23 | 24 | private final String temporaryTopicPrefix = "jms-temp-queue-"; 25 | 26 | @Override 27 | public String topicExchangeName() { 28 | return this.topicExchangeName; 29 | } 30 | 31 | @Override 32 | public String temporaryTopicExchangeName() { 33 | return this.temporaryTopicExchangeName; 34 | } 35 | 36 | @Override 37 | public String queueExchangeName() { 38 | return this.queueExchangeName; 39 | } 40 | 41 | @Override 42 | public String temporaryQueueExchangeName() { 43 | return this.temporaryQueueExchangeName; 44 | } 45 | 46 | @Override 47 | public String topicSubscriberQueuePrefix() { 48 | return this.topicSubscriberQueuePrefix; 49 | } 50 | 51 | @Override 52 | public String durableSubscriberTopicSelectorExchangePrefix() { 53 | return this.durableSubscriberTopicSelectorExchangePrefix; 54 | } 55 | 56 | @Override 57 | public String nonDurableSubscriberTopicSelectorExchangePrefix() { 58 | return this.nonDurableSubscriberTopicSelectorExchangePrefix; 59 | } 60 | 61 | @Override 62 | public String temporaryQueuePrefix() { 63 | return this.temporaryQueuePrefix; 64 | } 65 | 66 | @Override 67 | public String temporaryTopicPrefix() { 68 | return this.temporaryTopicPrefix; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test against RabbitMQ stable 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-24.04 15 | 16 | steps: 17 | - uses: actions/checkout@v6 18 | - name: Checkout tls-gen 19 | uses: actions/checkout@v6 20 | with: 21 | repository: rabbitmq/tls-gen 22 | path: './tls-gen' 23 | - name: Set up JDK 24 | uses: actions/setup-java@v5 25 | with: 26 | distribution: 'zulu' 27 | java-version: '21' 28 | cache: 'maven' 29 | server-id: central 30 | server-username: MAVEN_USERNAME 31 | server-password: MAVEN_PASSWORD 32 | gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} 33 | gpg-passphrase: MAVEN_GPG_PASSPHRASE 34 | - name: Start RabbitMQ application 35 | run: ci/start-broker.sh 36 | - name: Test 37 | run: | 38 | ./mvnw verify -Drabbitmqctl.bin=DOCKER:rabbitmq \ 39 | -Dtest-broker.A.nodename=rabbit@$(hostname) \ 40 | -Dtest-tls-certs.dir=tls-gen/basic \ 41 | --no-transfer-progress 42 | - name: Stop broker 43 | run: docker stop rabbitmq && docker rm rabbitmq 44 | - name: Publish snapshot 45 | run: ./mvnw clean deploy -Psnapshots -DskipITs -DskipTests --no-transfer-progress 46 | if: ${{ github.event_name != 'pull_request' }} 47 | env: 48 | MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }} 49 | MAVEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN }} 50 | MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 51 | - name: Generate documentation 52 | if: ${{ github.event_name != 'pull_request' }} 53 | run: make doc 54 | - name: Publish documentation 55 | if: ${{ github.event_name != 'pull_request' }} 56 | run: | 57 | git config user.name "rabbitmq-ci" 58 | git config user.email "rabbitmq-ci@users.noreply.github.com" 59 | ci/publish-documentation-to-github-pages.sh 60 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/sql/SqlParser.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse.sql; 3 | 4 | import com.rabbitmq.jms.parse.Parser; 5 | import com.rabbitmq.jms.parse.TokenStream; 6 | 7 | /** 8 | * This uses {@link SqlProduction} as a naïve parser for the grammar defined in that type. 9 | *

10 | * This class is not thread-safe during construction since it then modifies the passed {@link TokenStream} which is (potentially) shared. 11 | *

12 | */ 13 | public class SqlParser implements Parser { 14 | 15 | private final boolean parseOk; 16 | private final String errorMessage; 17 | private final SqlParseTree parseTree; 18 | 19 | public SqlParser(SqlTokenStream tokenStream) { 20 | if ("".equals(tokenStream.getResidue())) { 21 | this.parseTree = SqlProduction.ROOT.parse(tokenStream); 22 | if (tokenStream.moreTokens()) { 23 | this.parseOk = false; 24 | this.errorMessage = 25 | String.format("Terminated before end of stream; next token is: '%s' at index: %s." 26 | , tokenStream.readToken(), tokenStream.position()); 27 | } else if (this.parseTree == null) { 28 | this.parseOk = false; 29 | this.errorMessage = "No parse tree produced."; 30 | } else { 31 | this.parseOk = true; 32 | this.errorMessage = null; 33 | } 34 | } else { 35 | this.parseOk = false; 36 | this.errorMessage = "Unrecognised syntax after: '" + tokenStream.getResidue() + "'"; 37 | this.parseTree = null; 38 | } 39 | 40 | } 41 | 42 | @Override 43 | public SqlParseTree parse() { 44 | return this.parseTree; 45 | } 46 | 47 | @Override 48 | public boolean parseOk() { 49 | return this.parseOk; 50 | } 51 | 52 | @Override 53 | public String getErrorMessage() { 54 | return this.errorMessage; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/JMSMetaData.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import java.util.Enumeration; 9 | 10 | import jakarta.jms.JMSException; 11 | 12 | /** 13 | * Meta data for {@link JMSMetaData} 14 | */ 15 | class JMSMetaData { 16 | private static final String JMS_PROVIDER_NAME = "RabbitMQ"; 17 | private static final String JMS_VERSION = "1.1"; 18 | private static final int JMS_MAJOR_VERSION = 1; 19 | private static final int JMS_MINOR_VERSION = 1; 20 | 21 | /** 22 | * These two are currently not used, they are needed for a JMSCTS test 23 | * We need to make sure these properties get into the messages 24 | */ 25 | public static final String JMSX_GROUP_ID_LABEL = "JMSXGroupID"; // value should be VMW 26 | public static final String JMSX_GROUP_SEQ_LABEL = "JMSXGroupSeq"; 27 | 28 | public String getJMSVersion() throws JMSException { 29 | return JMS_VERSION; 30 | } 31 | 32 | public int getJMSMajorVersion() throws JMSException { 33 | return JMS_MAJOR_VERSION; 34 | } 35 | 36 | public int getJMSMinorVersion() throws JMSException { 37 | return JMS_MINOR_VERSION; 38 | } 39 | 40 | public String getJMSProviderName() throws JMSException { 41 | return JMS_PROVIDER_NAME; 42 | } 43 | 44 | static class JmsXEnumerator implements Enumeration { 45 | int idx = 0; 46 | @Override 47 | public boolean hasMoreElements() { 48 | return idx < 2; 49 | } 50 | 51 | @Override 52 | public String nextElement() { 53 | switch (idx++) { 54 | case 0 : return JMSX_GROUP_ID_LABEL; 55 | case 1 : return JMSX_GROUP_SEQ_LABEL; 56 | default: return null; 57 | } 58 | } 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/TokenStream.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | /** 3 | * A stream of tokens. Supports getNextToken() and putBackToken() operations as well as moreTokens(). 4 | */ 5 | package com.rabbitmq.jms.parse; 6 | 7 | /** 8 | * A read-only sequence of {@link Token}s that are read one-by-one. Reads are not destructive and the stream can be reset and reread. 9 | * At any time a position may be obtained, and subsequently used to reset the stream to that position. 10 | * 11 | * @param - the type of items in the stream 12 | * @param - the type which implements the positions in the stream 13 | */ 14 | public interface TokenStream { 15 | 16 | /** 17 | * @return true if there are more tokens after the current position in the stream 18 | */ 19 | boolean moreTokens(); 20 | 21 | /** 22 | * Reads token at the current position and the position is not changed. 23 | * @return the current {@link Token} in the stream if any, otherwise null 24 | */ 25 | Token readToken(); 26 | 27 | /** 28 | * Increment position in the stream; no-op if already at the end. 29 | * @return the next {@link Token} in the stream if any, otherwise null 30 | */ 31 | Token getNext(); 32 | 33 | /** 34 | * Decrement position in the stream; no-op if already at the beginning. 35 | */ 36 | void stepBack(); 37 | 38 | /** 39 | * @return the current position in the stream 40 | */ 41 | Position position(); 42 | 43 | /** 44 | * Reset the stream to the position provided, if it is a valid position. Otherwise undefined. 45 | * @param position - previously returned place in the stream 46 | */ 47 | void reset(Position position); 48 | 49 | /** 50 | * Reset the stream to the start. Equivalent to 51 | * tokenStream.reset(START) where Position START = tokenStream.getPosition(); 52 | * was executed first after instantiation of the {@link TokenStream}. 53 | */ 54 | void reset(); 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/ExceptionHandlingIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | 7 | package com.rabbitmq.integration.tests; 8 | 9 | import com.rabbitmq.jms.admin.RMQDestination; 10 | import org.junit.jupiter.api.AfterEach; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | 14 | import jakarta.jms.JMSException; 15 | import jakarta.jms.MessageConsumer; 16 | import jakarta.jms.QueueConnection; 17 | import jakarta.jms.QueueConnectionFactory; 18 | import jakarta.jms.Session; 19 | 20 | import static org.junit.jupiter.api.Assertions.assertThrows; 21 | 22 | /** 23 | * To make sure an exception is thrown when starting listening on 24 | * a non-existing destination. 25 | */ 26 | public class ExceptionHandlingIT { 27 | 28 | QueueConnectionFactory connFactory; 29 | QueueConnection queueConn; 30 | 31 | @BeforeEach 32 | public void beforeTests() throws Exception { 33 | this.connFactory = (QueueConnectionFactory) AbstractTestConnectionFactory.getTestConnectionFactory() 34 | .getConnectionFactory(); 35 | this.queueConn = connFactory.createQueueConnection(); 36 | } 37 | 38 | @AfterEach 39 | public void afterTests() throws Exception { 40 | if (queueConn != null) 41 | queueConn.close(); 42 | } 43 | 44 | @Test 45 | public void consumerOnNonExistingDestination() throws Exception { 46 | queueConn.start(); 47 | String queueName = ExceptionHandlingIT.class.getSimpleName() + " " + System.currentTimeMillis(); 48 | RMQDestination queue = new RMQDestination(queueName, null, null, queueName); 49 | Session session = queueConn.createSession(false, Session.AUTO_ACKNOWLEDGE); 50 | MessageConsumer messageConsumer = session.createConsumer(queue); 51 | 52 | assertThrows(JMSException.class, () -> messageConsumer.setMessageListener(msg -> { 53 | })); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/util/AbortableHolder.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.util; 3 | 4 | import java.util.concurrent.ConcurrentLinkedQueue; 5 | 6 | /** 7 | * Bag of {@link Abortable}s which is itself an {@link Abortable}. 8 | * {@link Abortable} actions are propagated to elements. 9 | */ 10 | public class AbortableHolder implements Abortable { 11 | private final java.util.Queue abortableQueue = new ConcurrentLinkedQueue(); 12 | private final boolean[] flags = new boolean[] { false, false, false }; // to prevent infinite regress 13 | 14 | private enum Action { 15 | ABORT(0) { @Override void doit(Abortable a) throws Exception { a.abort(); } }, 16 | START(1) { @Override void doit(Abortable a) throws Exception { a.start(); } }, 17 | STOP(2) { @Override void doit(Abortable a) throws Exception { a.stop(); } }; 18 | private final int ind; 19 | 20 | Action(int ind) { this.ind = ind; } 21 | 22 | int index() { return this.ind; } 23 | 24 | abstract void doit(Abortable a) throws Exception; 25 | }; 26 | 27 | /** 28 | * @param a - abortable to hold 29 | */ 30 | public void add(Abortable a) { 31 | this.abortableQueue.add(a); 32 | } 33 | 34 | /** 35 | * @param a - abortable to remove (once) if held 36 | */ 37 | public void remove(Abortable a) { 38 | this.abortableQueue.remove(a); 39 | } 40 | 41 | public void abort() throws Exception { act(Action.ABORT); } 42 | 43 | public void start() throws Exception { act(Action.START); } 44 | 45 | public void stop() throws Exception { act(Action.STOP); } 46 | 47 | private void act(AbortableHolder.Action action) throws Exception { 48 | if (this.flags[action.index()]) return; // prevent infinite 49 | this.flags[action.index()] = true; // regress 50 | 51 | Abortable[] as = this.abortableQueue.toArray(new Abortable[this.abortableQueue.size()]); 52 | for (Abortable a : as) { action.doit(a); } 53 | this.flags[action.index()] = false; // allow multiple invocations 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/admin/NamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.jms.admin; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Contract to control the name of AMQP entities (exchanges, queues). 7 | * 8 | *

Naming is considered an implementation details of RabbitMQ JMS, so this interface and its 9 | * methods can change at any time between releases. 10 | * 11 | *

A custom implementation can be used to customize the name of some entities (e.g. for security 12 | * reasons), but implementators must be prepared to API changes. 13 | * 14 | * @since 3.4.0 15 | * @see RMQConnectionFactory#setNamingStrategy(NamingStrategy) 16 | */ 17 | public interface NamingStrategy extends Serializable { 18 | 19 | NamingStrategy DEFAULT = new DefaultNamingStrategy(); 20 | 21 | /** 22 | * Name of the exchange for non-temporary topics. 23 | * 24 | * @return exchange name 25 | */ 26 | String topicExchangeName(); 27 | 28 | /** 29 | * Name of the exchange for temporary topics. 30 | * 31 | * @return exchange name 32 | */ 33 | String temporaryTopicExchangeName(); 34 | 35 | /** 36 | * Name of the exchange for non-temporary queues. 37 | * 38 | * @return exchange name 39 | */ 40 | String queueExchangeName(); 41 | 42 | /** 43 | * Name of the exchange for temporary queues. 44 | * 45 | * @return exchange name 46 | */ 47 | String temporaryQueueExchangeName(); 48 | 49 | /** 50 | * Prefix for the AMQP queue name of topic subscribers. 51 | * 52 | * @return queue prefix 53 | */ 54 | String topicSubscriberQueuePrefix(); 55 | 56 | /** 57 | * Prefix for the selector exchange name of durable topic subscribers. 58 | * 59 | * @return exchange prefix 60 | */ 61 | String durableSubscriberTopicSelectorExchangePrefix(); 62 | 63 | /** 64 | * Prefix for the selector exchange name of non-durable topic subscribers. 65 | * 66 | * @return exchange prefix 67 | */ 68 | String nonDurableSubscriberTopicSelectorExchangePrefix(); 69 | 70 | /** 71 | * Prefix for the AMQP queue name of temporary queues. 72 | * 73 | * @return queue prefix 74 | */ 75 | String temporaryQueuePrefix(); 76 | 77 | /** 78 | * Prefix for the AMQP queue name of temporary topics. 79 | * 80 | * @return queue prefix 81 | */ 82 | String temporaryTopicPrefix(); 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/RmqDestinationIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | 7 | package com.rabbitmq.integration.tests; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | import com.rabbitmq.jms.admin.RMQDestination; 12 | import com.rabbitmq.jms.admin.RMQDestinationTest; 13 | import java.util.Collections; 14 | import java.util.concurrent.CountDownLatch; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.concurrent.atomic.AtomicReference; 17 | import jakarta.jms.MessageConsumer; 18 | import jakarta.jms.Queue; 19 | import jakarta.jms.QueueSender; 20 | import jakarta.jms.QueueSession; 21 | import jakarta.jms.Session; 22 | import jakarta.jms.TextMessage; 23 | import org.junit.jupiter.api.Test; 24 | 25 | public class RmqDestinationIT extends AbstractITQueue { 26 | 27 | private static final String QUEUE_NAME = 28 | "test.queue." + RMQDestinationTest.class.getCanonicalName(); 29 | 30 | @Test 31 | void createRmqDestinationWithArguments() throws Exception { 32 | this.queueConn.start(); 33 | QueueSession session = this.queueConn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); 34 | 35 | Queue queue = 36 | new RMQDestination( 37 | QUEUE_NAME, true, false, Collections.singletonMap("x-queue-type", "quorum")); 38 | QueueSender sender = session.createSender(queue); 39 | drainQueue(session, queue); 40 | String body = String.valueOf(System.currentTimeMillis()); 41 | TextMessage msg = session.createTextMessage(body); 42 | sender.send(msg); 43 | 44 | CountDownLatch consumerLatch = new CountDownLatch(1); 45 | AtomicReference message = new AtomicReference<>(); 46 | MessageConsumer consumer = session.createConsumer(queue); 47 | consumer.setMessageListener( 48 | m -> { 49 | message.set((TextMessage) m); 50 | consumerLatch.countDown(); 51 | }); 52 | assertThat(consumerLatch.await(10, TimeUnit.SECONDS)).isTrue(); 53 | assertThat(message.get().getText()).isEqualTo(body); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/RabbitAPIConnectionFactory.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import jakarta.jms.Connection; 9 | import jakarta.jms.ConnectionFactory; 10 | import jakarta.jms.JMSException; 11 | 12 | import com.rabbitmq.jms.admin.RMQConnectionFactory; 13 | 14 | import java.security.NoSuchAlgorithmException; 15 | 16 | /** 17 | * Connection factory for use in integration tests. 18 | */ 19 | public class RabbitAPIConnectionFactory extends AbstractTestConnectionFactory { 20 | 21 | private static final int RABBIT_PORT = 5672; // 5672 default; 5673 Tracer. 22 | private static final int RABBIT_TLS_PORT = 5671; 23 | private final boolean testssl; 24 | private int qbrMax; 25 | 26 | public RabbitAPIConnectionFactory() { this(false); } 27 | 28 | public RabbitAPIConnectionFactory(boolean testssl) { this(testssl, 0); } 29 | 30 | public RabbitAPIConnectionFactory(boolean testssl, int qbrMax) { this.testssl = testssl; 31 | this.qbrMax = qbrMax; 32 | } 33 | 34 | @Override 35 | public ConnectionFactory getConnectionFactory() { 36 | RMQConnectionFactory rmqCF = new RMQConnectionFactory() { 37 | private static final long serialVersionUID = 1L; 38 | @Override 39 | public Connection createConnection(String userName, String password) throws JMSException { 40 | if (!testssl) { 41 | this.setPort(RABBIT_PORT); 42 | } else { 43 | this.setPort(RABBIT_TLS_PORT); 44 | } 45 | return super.createConnection(userName, password); 46 | } 47 | }; 48 | if (testssl) { 49 | try { 50 | rmqCF.useSslProtocol(); 51 | } catch (NoSuchAlgorithmException e) { 52 | throw new RuntimeException(e); 53 | } 54 | } 55 | rmqCF.setQueueBrowserReadMax(qbrMax); 56 | return rmqCF; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open 4 | and welcoming community, we pledge to respect all people who contribute through reporting 5 | issues, posting feature requests, updating documentation, submitting pull requests or 6 | patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free experience for 9 | everyone, regardless of level of experience, gender, gender identity and expression, 10 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, 11 | religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic addresses, 20 | without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 24 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 25 | Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors 26 | that they deem inappropriate, threatening, offensive, or harmful. 27 | 28 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and 29 | consistently applying these principles to every aspect of managing this project. Project 30 | maintainers who do not follow or enforce the Code of Conduct may be permanently removed 31 | from the project team. 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an 34 | individual is representing the project or its community. 35 | 36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 37 | contacting a project maintainer at [info@rabbitmq.com](mailto:info@rabbitmq.com). All complaints will 38 | be reviewed and investigated and will result in a response that is deemed necessary and 39 | appropriate to the circumstances. Maintainers are obligated to maintain confidentiality 40 | with regard to the reporter of an incident. 41 | 42 | This Code of Conduct is adapted from the 43 | [Contributor Covenant](https://contributor-covenant.org), version 1.3.0, available at 44 | [contributor-covenant.org/version/1/3/0/](https://contributor-covenant.org/version/1/3/0/) 45 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/jms/client/DefaultReplyToStrategyTest.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.verify; 10 | import static org.mockito.Mockito.verifyNoInteractions; 11 | import static org.mockito.Mockito.verifyNoMoreInteractions; 12 | 13 | import jakarta.jms.JMSException; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Test; 16 | 17 | import com.rabbitmq.jms.admin.RMQDestination; 18 | 19 | public class DefaultReplyToStrategyTest { 20 | 21 | DefaultReplyToStrategy strategy; 22 | 23 | RMQMessage message; 24 | 25 | @BeforeEach 26 | void setUp() { 27 | strategy = new DefaultReplyToStrategy(); 28 | message = mock(RMQMessage.class); 29 | } 30 | 31 | 32 | @Test 33 | void noReplyTo() throws JMSException { 34 | strategy.handleReplyTo(message, null); 35 | verifyNoInteractions(message); 36 | } 37 | 38 | @Test 39 | void emptyReplyTo() throws JMSException { 40 | strategy.handleReplyTo(message, ""); 41 | verifyNoInteractions(message); 42 | } 43 | 44 | @Test 45 | void nonDirectReplyTo() throws JMSException { 46 | strategy.handleReplyTo(message, "non direct reply to"); 47 | verifyNoInteractions(message); 48 | } 49 | 50 | @Test 51 | void directReplyTo() throws JMSException { 52 | strategy.handleReplyTo(message, ReplyToStrategy.DIRECT_REPLY_TO); 53 | RMQDestination d = new RMQDestination(ReplyToStrategy.DIRECT_REPLY_TO, "", ReplyToStrategy.DIRECT_REPLY_TO, ReplyToStrategy.DIRECT_REPLY_TO); 54 | 55 | verify(message).setJMSReplyTo(d); 56 | verifyNoMoreInteractions(message); 57 | } 58 | 59 | @Test 60 | void directReplyToqueue() throws JMSException { 61 | strategy.handleReplyTo(message, ReplyToStrategy.DIRECT_REPLY_TO + "-123456678"); 62 | 63 | RMQDestination d = new RMQDestination(ReplyToStrategy.DIRECT_REPLY_TO, "", ReplyToStrategy.DIRECT_REPLY_TO + "-123456678", ReplyToStrategy.DIRECT_REPLY_TO + "-123456678"); 64 | 65 | verify(message).setJMSReplyTo(d); 66 | verifyNoMoreInteractions(message); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/ParseTreeTraverser.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse; 3 | 4 | /** 5 | * This traverser class encapsulates two methods of traversing a tree, calling a {@link Visitor} for 6 | * each subtree of a {@link ParseTree ParseTree<Node>}. 7 | *

8 | * Each subtree can be visited in ‘pre-order’ as well asO 9 | * in ‘post-order’. ‘Pre-order’ visits each subtree before any of its child subtrees, and ‘post-order’ 10 | * visits each subtree after visiting its child subtrees. 11 | *

12 | *

13 | * {@link ParseTreeTraverser#traverse(ParseTree, Visitor)} visits each subtree twice: combining ‘pre-order’ and ‘post-order’ traversal in one pass. 14 | *

15 | *

16 | * The {@link Visitor} contract returns a boolean flag from its visit methods which these 17 | * traversal algorithms interpret as immediate abort: no further visits will be made after the first returns false. 18 | * Each traversal method returns true if all the tree is visited, and false if any visit aborted. 19 | *

20 | */ 21 | public abstract class ParseTreeTraverser { // stop direct instantiation 22 | private ParseTreeTraverser() {}; // stop indirect instantiation 23 | 24 | /** 25 | * Visits each subtree in both ‘pre-order’ and ‘post-order’, in one pass. 26 | * Each subtree is visited before (by calling {@link Visitor#visitBefore(Object, Object[])}) and after (by calling {@link Visitor#visitAfter(Object, Object[])}) 27 | * all of its child subtrees are visited. 28 | * @param tree the tree all of whose subtrees are to be visited 29 | * @param visitor the {@link Visitor} whose {@link Visitor#visitBefore(Object, Object[])} and {@link Visitor#visitAfter(Object, Object[])} 30 | * methods are passed each subtree’s node and child nodes. 31 | * @return true if the traversal completed, false if it was aborted prematurely 32 | */ 33 | public static boolean traverse(ParseTree tree, Visitor visitor) { 34 | if (!visitor.visitBefore(tree.getNode(), tree.getChildNodes())) return false; 35 | for (ParseTree st : tree.getChildren()) { 36 | if (!traverse(st, visitor)) return false; 37 | } 38 | if (!visitor.visitAfter(tree.getNode(), tree.getChildNodes())) return false; 39 | return true; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/sql/SqlEvaluator.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse.sql; 3 | 4 | import static com.rabbitmq.jms.parse.ParseTreeTraverser.traverse; 5 | 6 | import java.util.Map; 7 | 8 | import com.rabbitmq.jms.parse.Evaluator; 9 | 10 | /** 11 | * A boolean evaluator for JMS Sql selector expressions. 12 | * 13 | */ 14 | public class SqlEvaluator implements Evaluator { 15 | 16 | private final SqlParseTree typedParseTree; 17 | private final String errorMessage; 18 | private final boolean evaluatorOk; 19 | 20 | public SqlEvaluator(SqlParser parser, Map identTypes) { 21 | if (parser.parseOk()) { 22 | SqlParseTree parseTree = parser.parse(); 23 | if (this.evaluatorOk = canBeBool(SqlTypeChecker.deriveExpressionType(parseTree, identTypes))) { 24 | this.typedParseTree = parseTree; 25 | this.errorMessage = null; 26 | } else { 27 | this.errorMessage = "Type error in expression"; 28 | this.typedParseTree = null; 29 | } 30 | } else { 31 | this.evaluatorOk = false; 32 | this.typedParseTree = null; 33 | this.errorMessage = parser.getErrorMessage(); 34 | } 35 | } 36 | 37 | private static boolean canBeBool(SqlExpressionType set) { 38 | return (set == SqlExpressionType.BOOL || set == SqlExpressionType.ANY); 39 | } 40 | 41 | @Override 42 | public boolean evaluate(Map env) { 43 | if (this.evaluatorOk){ 44 | SqlEvaluatorVisitor eVisitor = new SqlEvaluatorVisitor(env); 45 | if (traverse(this.typedParseTree, eVisitor)) { 46 | Object val = this.typedParseTree.getNode().getExpValue().getValue(); 47 | if (val != null && val instanceof Boolean) 48 | return (Boolean) val; 49 | } 50 | } 51 | return false; 52 | } 53 | 54 | /** 55 | * @return the type-checked parse tree used for evaluation; null if {@link #evaluatorOk()} is false. 56 | */ 57 | public SqlParseTree typedParseTree() { 58 | return this.typedParseTree; 59 | } 60 | 61 | @Override 62 | public boolean evaluatorOk() { 63 | return this.evaluatorOk; 64 | } 65 | 66 | @Override 67 | public String getErrorMessage() { 68 | return this.errorMessage; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/parse/sql/SqlParseTree.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.parse.sql; 3 | 4 | import java.util.ArrayList; 5 | 6 | import com.rabbitmq.jms.parse.ParseTree; 7 | 8 | /** 9 | * Tree used to hold a parsed SQL (selector) expression. The nodes are of type {@link SqlTreeNode}. 10 | * 11 | */ 12 | class SqlParseTree implements ParseTree { 13 | 14 | private final SqlTreeNode node; 15 | private final SqlParseTree[] children; 16 | private final SqlTreeNode[] nodes; 17 | 18 | /** 19 | * Tree constructor: takes the node value and the children; both are final fields of the tree. 20 | * @param node - the node value of the (root of the) tree. 21 | * @param children - a series of trees; omitting these arguments will imply a zero-length array of trees. 22 | */ 23 | public SqlParseTree(SqlTreeNode node, SqlParseTree... children) { 24 | this.node = node; 25 | this.children = children; 26 | this.nodes = genNodes(children); 27 | } 28 | 29 | @Override 30 | public SqlTreeNode getNode() { 31 | return this.node; 32 | } 33 | 34 | @Override 35 | public int getNumberOfChildren() { 36 | return this.children.length; 37 | } 38 | 39 | @Override 40 | public SqlParseTree[] getChildren() { 41 | return this.children; 42 | } 43 | 44 | /** 45 | * @return an array of {@link String}s which form a readable representation of the tree. 46 | */ 47 | String[] formattedTree() { 48 | ArrayList lines = new ArrayList(); 49 | SqlToken tokVal = this.node.value(); 50 | lines.add(this.node.treeType().toString() + (tokVal == null ? ":" : ": " + tokVal)); 51 | for (SqlParseTree child: this.children) { 52 | String[] childLines = child.formattedTree(); 53 | for (String childLine: childLines) { 54 | lines.add(" " + childLine); 55 | } 56 | } 57 | return lines.toArray(new String[lines.size()]); 58 | } 59 | 60 | @Override 61 | public SqlTreeNode[] getChildNodes() { 62 | return this.nodes; 63 | } 64 | 65 | private static final SqlTreeNode[] genNodes(SqlParseTree[] children) { 66 | SqlTreeNode[] nodes = new SqlTreeNode[children.length]; 67 | for (int i=0; i 11 | * 12 | *

13 | */ 14 | class SqlExpressionValue { 15 | 16 | private SqlExpressionType expType; 17 | private Object expValue; 18 | // INVARIANT: the expValue must either be null or be an object of type consistent with expType; 19 | // one of: 20 | // NOT_SET(null), BOOL(Boolean), ARITH(Float, Double, Integer, Long), STRING(String), LIST(List) 21 | // but not INVALID. 22 | // ANY can be any of the above, except LIST. 23 | 24 | SqlExpressionValue() { 25 | this(SqlExpressionType.NOT_SET, null); 26 | } 27 | 28 | private SqlExpressionValue(SqlExpressionType expType, Object expValue) { 29 | this.expType = expType; 30 | this.expValue = filterValType(expType, expValue); 31 | } 32 | 33 | SqlExpressionType getType() { 34 | return this.expType; 35 | } 36 | 37 | void setType(SqlExpressionType expType) { 38 | this.expType = expType; 39 | this.expValue = null; 40 | } 41 | 42 | Object getValue() { 43 | return this.expValue; 44 | } 45 | 46 | void setValue(Object val) { 47 | this.expValue = filterValType(this.expType, val); 48 | } 49 | 50 | private static final Object filterValType(SqlExpressionType type, Object val) { 51 | if (val == null) return null; 52 | switch (type) { 53 | case ANY: return filter(val, String.class, Boolean.class, Float.class, Double.class, Integer.class, Long.class); 54 | case ARITH: return filter(val, Float.class, Double.class, Integer.class, Long.class); 55 | case BOOL: return filter(val, Boolean.class); 56 | case LIST: return filter(val, List.class); 57 | case STRING: return filter(val, String.class); 58 | default: return null; 59 | } 60 | } 61 | 62 | private static final Object filter(Object val, Class ... clzs) { 63 | for (Class clz : clzs) if (clz.isInstance(val)) return val; 64 | return null; 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return String.format("<%s: %s>", this.expType, this.expValue); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/GenericVersion.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2014-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | class GenericVersion { 9 | 10 | private final int major; 11 | private final int minor; 12 | private final int micro; 13 | private final String qualifier; 14 | 15 | public GenericVersion(int major, int minor, int micro, String qualifier) { 16 | this.major = major; 17 | this.minor = minor; 18 | this.micro = micro; 19 | this.qualifier = non_null(qualifier); 20 | } 21 | 22 | public GenericVersion(int major, int minor, int micro) { 23 | this(major, minor, micro, ""); 24 | } 25 | 26 | public GenericVersion(int major, int minor) { 27 | this(major, minor, 0, ""); 28 | } 29 | 30 | public GenericVersion(int major) { 31 | this(major, 0, 0, ""); 32 | } 33 | 34 | public GenericVersion(String versionString) { 35 | int mode=0; 36 | int ver[] = new int[] {0,0,0}; 37 | StringBuilder qual=new StringBuilder(); 38 | for (char ch : non_null(versionString).toCharArray()) { 39 | if (mode==3) { qual.append(ch); } 40 | else if (Character.isDigit(ch)) { ver[mode] = 10*ver[mode] + Character.digit(ch, 10); } 41 | else if (ch == '.') { ++mode; } 42 | else { mode=3; qual.append(ch); } 43 | } 44 | this.major = ver[0]; 45 | this.minor = ver[1]; 46 | this.micro = ver[2]; 47 | this.qualifier = qual.toString(); 48 | } 49 | 50 | public int getMajor() { 51 | return major; 52 | } 53 | 54 | public int getMinor() { 55 | return minor; 56 | } 57 | 58 | public int getMicro() { 59 | return micro; 60 | } 61 | 62 | public String getQualifier() { 63 | return qualifier; 64 | } 65 | 66 | public String toString() { 67 | return new StringBuilder() 68 | .append(this.major).append('.') 69 | .append(this.minor).append('.') 70 | .append(this.micro).append(this.qualifier) 71 | .toString(); 72 | } 73 | 74 | private static final String non_null(String str) { return (str==null ? "" : str); } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/SelectedTopicMessageIdentifierIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | import jakarta.jms.DeliveryMode; 11 | import jakarta.jms.Session; 12 | import jakarta.jms.TextMessage; 13 | import jakarta.jms.Topic; 14 | import jakarta.jms.TopicPublisher; 15 | import jakarta.jms.TopicSession; 16 | import jakarta.jms.TopicSubscriber; 17 | 18 | import org.junit.jupiter.api.Test; 19 | 20 | import com.rabbitmq.jms.client.message.RMQTextMessage; 21 | 22 | /** 23 | * Integration test 24 | */ 25 | public class SelectedTopicMessageIdentifierIT extends AbstractITTopic { 26 | private static final String TOPIC_NAME = "test.topic." + SelectedTopicMessageIdentifierIT.class.getCanonicalName(); 27 | private static final String MESSAGE1 = "Hello (1) " + SelectedTopicMessageIdentifierIT.class.getName(); 28 | private static final String MESSAGE2 = "Hello (2) " + SelectedTopicMessageIdentifierIT.class.getName(); 29 | 30 | @Test 31 | public void testSendAndReceiveTextMessagesWithSelectorsAndPeriodIdentifiers() throws Exception { 32 | topicConn.start(); 33 | TopicSession topicSession = topicConn.createTopicSession(false, Session.DUPS_OK_ACKNOWLEDGE); 34 | Topic topic = topicSession.createTopic(TOPIC_NAME); 35 | TopicPublisher sender = topicSession.createPublisher(topic); 36 | TopicSubscriber receiver1 = topicSession.createSubscriber(topic, "period.bool.prop", false); 37 | TopicSubscriber receiver2 = topicSession.createSubscriber(topic, "not period.bool.prop", false); 38 | 39 | sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 40 | 41 | TextMessage message1 = topicSession.createTextMessage(MESSAGE1); 42 | TextMessage message2 = topicSession.createTextMessage(MESSAGE2); 43 | 44 | message1.setBooleanProperty("period.bool.prop", true); 45 | message2.setBooleanProperty("period.bool.prop", false); 46 | 47 | sender.send(message1); 48 | sender.send(message2); 49 | 50 | RMQTextMessage tmsg1 = (RMQTextMessage) receiver1.receive(); 51 | RMQTextMessage tmsg2 = (RMQTextMessage) receiver2.receive(); 52 | 53 | String t1 = tmsg1.getText(); 54 | String t2 = tmsg2.getText(); 55 | assertEquals(MESSAGE1, t1); 56 | assertEquals(MESSAGE2, t2); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/jms/client/HandleAnyReplyToStrategyTest.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.verify; 10 | import static org.mockito.Mockito.verifyNoInteractions; 11 | import static org.mockito.Mockito.verifyNoMoreInteractions; 12 | 13 | import jakarta.jms.JMSException; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Test; 16 | 17 | import com.rabbitmq.jms.admin.RMQDestination; 18 | 19 | public class HandleAnyReplyToStrategyTest { 20 | 21 | HandleAnyReplyToStrategy strategy; 22 | 23 | RMQMessage message; 24 | 25 | @BeforeEach 26 | void setUp() { 27 | strategy = new HandleAnyReplyToStrategy(); 28 | message = mock(RMQMessage.class); 29 | } 30 | 31 | 32 | @Test 33 | void noReplyTo() throws JMSException { 34 | strategy.handleReplyTo(message, null); 35 | 36 | verifyNoInteractions(message); 37 | } 38 | 39 | @Test 40 | void emptyReplyTo() throws JMSException { 41 | strategy.handleReplyTo(message, ""); 42 | 43 | verifyNoInteractions(message); 44 | } 45 | 46 | @Test 47 | void nonDirectReplyTo() throws JMSException { 48 | strategy.handleReplyTo(message, "non direct reply to"); 49 | 50 | RMQDestination d = new RMQDestination("non direct reply to", "", "non direct reply to", "non direct reply to"); 51 | 52 | verify(message).setJMSReplyTo(d); 53 | verifyNoMoreInteractions(message); 54 | } 55 | 56 | @Test 57 | void directReplyTo() throws JMSException { 58 | 59 | strategy.handleReplyTo(message, ReplyToStrategy.DIRECT_REPLY_TO); 60 | 61 | RMQDestination d = new RMQDestination(ReplyToStrategy.DIRECT_REPLY_TO, "", ReplyToStrategy.DIRECT_REPLY_TO, ReplyToStrategy.DIRECT_REPLY_TO); 62 | 63 | verify(message).setJMSReplyTo(d); 64 | verifyNoMoreInteractions(message); 65 | } 66 | 67 | @Test 68 | void directReplyToqueue() throws JMSException { 69 | strategy.handleReplyTo(message, ReplyToStrategy.DIRECT_REPLY_TO + "-123456678"); 70 | 71 | RMQDestination d = new RMQDestination(ReplyToStrategy.DIRECT_REPLY_TO, "", ReplyToStrategy.DIRECT_REPLY_TO + "-123456678", ReplyToStrategy.DIRECT_REPLY_TO + "-123456678"); 72 | 73 | verify(message).setJMSReplyTo(d); 74 | verifyNoMoreInteractions(message); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/TemporaryQueueIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | 11 | import jakarta.jms.DeliveryMode; 12 | import jakarta.jms.Queue; 13 | import jakarta.jms.QueueReceiver; 14 | import jakarta.jms.QueueSender; 15 | import jakarta.jms.QueueSession; 16 | import jakarta.jms.Session; 17 | import jakarta.jms.TextMessage; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | /** 22 | * Integration test 23 | */ 24 | public class TemporaryQueueIT extends AbstractITQueue { 25 | 26 | private static final String MESSAGE = "Hello " + TemporaryQueueIT.class.getName(); 27 | 28 | @Test 29 | public void testQueueSendAndReceiveSingleSession() throws Exception { 30 | queueConn.start(); 31 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 32 | Queue queue = queueSession.createTemporaryQueue(); 33 | QueueSender queueSender = queueSession.createSender(queue); 34 | queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 35 | TextMessage message = queueSession.createTextMessage(MESSAGE); 36 | queueSender.send(message); 37 | QueueReceiver queueReceiver = queueSession.createReceiver(queue); 38 | message = (TextMessage) queueReceiver.receive(1000); 39 | assertNotNull(message); 40 | assertEquals(MESSAGE, message.getText()); 41 | } 42 | 43 | @Test 44 | public void testQueueSendAndReceiveTwoSessions() throws Exception { 45 | queueConn.start(); 46 | QueueSession queueSession1 = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 47 | QueueSession queueSession2 = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 48 | Queue queue = queueSession1.createTemporaryQueue(); 49 | QueueSender queueSender = queueSession1.createSender(queue); 50 | queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 51 | TextMessage message = queueSession1.createTextMessage(MESSAGE); 52 | queueSender.send(message); 53 | QueueReceiver queueReceiver = queueSession2.createReceiver(queue); 54 | message = (TextMessage) queueReceiver.receive(1000); 55 | assertNotNull(message); 56 | assertEquals(MESSAGE, message.getText()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/AmqpPropertiesCustomiserIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import com.rabbitmq.client.GetResponse; 9 | import com.rabbitmq.jms.admin.RMQConnectionFactory; 10 | import com.rabbitmq.jms.admin.RMQDestination; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import jakarta.jms.Queue; 14 | import jakarta.jms.QueueSender; 15 | import jakarta.jms.QueueSession; 16 | import jakarta.jms.Session; 17 | import jakarta.jms.TextMessage; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertNotNull; 21 | 22 | /** 23 | * Integration test to test the AMQP properties customiser is applied correctly. 24 | */ 25 | public class AmqpPropertiesCustomiserIT extends AbstractAmqpITQueue { 26 | 27 | public static final String MESSAGE = "hello"; 28 | public static final String TEXT_PLAIN = "text/plain"; 29 | private static final String QUEUE_NAME = "test.queue." + AmqpPropertiesCustomiserIT.class.getCanonicalName(); 30 | 31 | @Override 32 | protected void customise(RMQConnectionFactory connectionFactory) { 33 | connectionFactory.setAmqpPropertiesCustomiser((builder, message) -> builder.contentType(TEXT_PLAIN)); 34 | } 35 | 36 | @Test 37 | public void customiserIsApplied() throws Exception { 38 | 39 | channel.queueDeclare(QUEUE_NAME, 40 | false, // durable 41 | true, // exclusive 42 | true, // autoDelete 43 | null // options 44 | ); 45 | 46 | queueConn.start(); 47 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 48 | Queue queue = new RMQDestination(QUEUE_NAME, "", QUEUE_NAME, null); // write-only AMQP-mapped queue 49 | 50 | QueueSender queueSender = queueSession.createSender(queue); 51 | 52 | TextMessage message = queueSession.createTextMessage(MESSAGE); 53 | 54 | queueSender.send(message); 55 | queueConn.close(); 56 | 57 | GetResponse response = channel.basicGet(QUEUE_NAME, false); 58 | assertNotNull(response, "basicGet failed to retrieve a response"); 59 | 60 | byte[] body = response.getBody(); 61 | assertNotNull(body, "body of response is null"); 62 | 63 | assertEquals(new String(body), MESSAGE, "body of response is not correct"); 64 | 65 | assertEquals(response.getProps().getContentType(), TEXT_PLAIN, "body of response is not correct"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/ReceiveNoWaitIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import java.io.Serializable; 9 | 10 | import jakarta.jms.DeliveryMode; 11 | import jakarta.jms.Queue; 12 | import jakarta.jms.QueueReceiver; 13 | import jakarta.jms.QueueSender; 14 | import jakarta.jms.QueueSession; 15 | import jakarta.jms.Session; 16 | 17 | import org.junit.jupiter.api.Test; 18 | 19 | /** 20 | * Integration test for simple browsing of a queue. 21 | */ 22 | public class ReceiveNoWaitIT extends AbstractITQueue { 23 | 24 | private static final String QUEUE_NAME = "test.queue."+ReceiveNoWaitIT.class.getCanonicalName(); 25 | 26 | private void messageTestBase(MessageTestType mtt) throws Exception { 27 | try { 28 | queueConn.start(); 29 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 30 | Queue queue = queueSession.createQueue(QUEUE_NAME); 31 | 32 | drainQueue(queueSession, queue); 33 | 34 | QueueSender queueSender = queueSession.createSender(queue); 35 | queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 36 | queueSender.send(mtt.gen(queueSession, (Serializable)queue)); 37 | } finally { 38 | reconnect(); 39 | } 40 | 41 | queueConn.start(); 42 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 43 | Queue queue = queueSession.createQueue(QUEUE_NAME); 44 | QueueReceiver queueReceiver = queueSession.createReceiver(queue); 45 | mtt.check(queueReceiver.receiveNoWait(), (Serializable)queue); 46 | } 47 | 48 | @Test 49 | public void testSendBrowseAndReceiveLongTextMessage() throws Exception { 50 | messageTestBase(MessageTestType.LONG_TEXT); 51 | } 52 | 53 | @Test 54 | public void testSendBrowseAndReceiveTextMessage() throws Exception { 55 | messageTestBase(MessageTestType.TEXT); 56 | } 57 | 58 | @Test 59 | public void testSendBrowseAndReceiveBytesMessage() throws Exception { 60 | messageTestBase(MessageTestType.BYTES); 61 | } 62 | 63 | @Test 64 | public void testSendBrowseAndReceiveMapMessage() throws Exception { 65 | messageTestBase(MessageTestType.MAP); 66 | } 67 | 68 | @Test 69 | public void testSendBrowseAndReceiveStreamMessage() throws Exception { 70 | messageTestBase(MessageTestType.STREAM); 71 | } 72 | 73 | @Test 74 | public void testSendBrowseAndReceiveObjectMessage() throws Exception { 75 | messageTestBase(MessageTestType.OBJECT); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/util/HexDisplay.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. */ 2 | package com.rabbitmq.jms.util; 3 | 4 | public class HexDisplay { 5 | /** ---------------------------------------- XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX | cccccccccccccccc */ 6 | private static final String LineTemplate = " | "; 7 | private static final int IndexFirstChar = 43; // | 8 | 9 | private static final char[] HEXCHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 10 | private static final char[] SAFECHARS = {'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', 11 | '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', 12 | ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', 13 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', 14 | '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 15 | 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', 16 | '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 17 | 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '.'}; 18 | 19 | public static final void decodeByteArrayIntoStringBuilder(byte[] buf, StringBuilder bufferOutput) { 20 | StringBuilder lineBuf = new StringBuilder(); 21 | for (int startOfLine=0; startOfLine>4) & 0x0F]); 36 | lineBuf.setCharAt(pos, HEXCHARS[ b & 0x0F]); 37 | } 38 | 39 | private static final char safeChar(byte b) { 40 | return SAFECHARS[b<0 ? 0 : b]; 41 | } 42 | 43 | private static final int displayPosOfHiHex(int i) { 44 | return (i/2)*5 + (i%2 == 0 ? 1 : 3); 45 | } 46 | 47 | private static final int min(int a, int b) { 48 | return a sentCount.incrementAndGet()); 46 | connection = connectionFactory.createConnection(); 47 | connection.start(); 48 | } 49 | 50 | @AfterEach 51 | public void tearDown() throws Exception { 52 | if (connection != null) { 53 | connection.close(); 54 | } 55 | com.rabbitmq.client.ConnectionFactory cf = new com.rabbitmq.client.ConnectionFactory(); 56 | try (com.rabbitmq.client.Connection c = cf.newConnection()) { 57 | c.createChannel().queueDelete(QUEUE_NAME); 58 | } 59 | } 60 | 61 | @Test 62 | public void sendingContextConsumerShouldBeCalledWhenSendingMessage() throws Exception { 63 | Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); 64 | TextMessage message = session.createTextMessage("hello"); 65 | MessageProducer producer = session.createProducer(session.createQueue(QUEUE_NAME)); 66 | int initialCount = sentCount.get(); 67 | producer.send(message); 68 | assertEquals(initialCount + 1, sentCount.get()); 69 | producer.send(message); 70 | assertEquals(initialCount + 2, sentCount.get()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/SimpleQueueMessageDefaultsIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import java.io.Serializable; 9 | 10 | import jakarta.jms.Message; 11 | import jakarta.jms.Queue; 12 | import jakarta.jms.QueueReceiver; 13 | import jakarta.jms.QueueSender; 14 | import jakarta.jms.QueueSession; 15 | import jakarta.jms.Session; 16 | 17 | import org.junit.jupiter.api.Test; 18 | 19 | /** 20 | * Integration test for simple browsing of a queue. 21 | */ 22 | public class SimpleQueueMessageDefaultsIT extends AbstractITQueue { 23 | 24 | private static final String QUEUE_NAME = "test.queue."+SimpleQueueMessageDefaultsIT.class.getCanonicalName(); 25 | private static final long TEST_RECEIVE_TIMEOUT = 1000; // one second 26 | 27 | private void messageTestBase(MessageTestType mtt) throws Exception { 28 | try { 29 | queueConn.start(); 30 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 31 | Queue queue = queueSession.createQueue(QUEUE_NAME); 32 | 33 | drainQueue(queueSession, queue); 34 | 35 | QueueSender queueSender = queueSession.createSender(queue); 36 | queueSender.send(mtt.gen(queueSession, (Serializable)queue)); 37 | } finally { 38 | reconnect(); 39 | } 40 | 41 | queueConn.start(); 42 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 43 | Queue queue = queueSession.createQueue(QUEUE_NAME); 44 | QueueReceiver queueReceiver = queueSession.createReceiver(queue); 45 | Message m = queueReceiver.receive(TEST_RECEIVE_TIMEOUT); 46 | mtt.check(m, (Serializable)queue); 47 | mtt.checkAttrs(m, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY); 48 | } 49 | 50 | @Test 51 | public void testSendAndReceiveLongTextMessage() throws Exception { 52 | messageTestBase(MessageTestType.LONG_TEXT); 53 | } 54 | 55 | @Test 56 | public void testSendAndReceiveTextMessage() throws Exception { 57 | messageTestBase(MessageTestType.TEXT); 58 | } 59 | 60 | @Test 61 | public void testSendAndReceiveBytesMessage() throws Exception { 62 | messageTestBase(MessageTestType.BYTES); 63 | } 64 | 65 | @Test 66 | public void testSendAndReceiveMapMessage() throws Exception { 67 | messageTestBase(MessageTestType.MAP); 68 | } 69 | 70 | @Test 71 | public void testSendAndReceiveStreamMessage() throws Exception { 72 | messageTestBase(MessageTestType.STREAM); 73 | } 74 | 75 | @Test 76 | public void testSendAndReceiveObjectMessage() throws Exception { 77 | messageTestBase(MessageTestType.OBJECT); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/SSLSimpleQueueMessageIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import com.rabbitmq.TestUtils.SkipIfTlsNotActivated; 9 | import java.io.Serializable; 10 | 11 | import jakarta.jms.DeliveryMode; 12 | import jakarta.jms.Queue; 13 | import jakarta.jms.QueueReceiver; 14 | import jakarta.jms.QueueSender; 15 | import jakarta.jms.QueueSession; 16 | import jakarta.jms.Session; 17 | 18 | import org.junit.jupiter.api.Test; 19 | 20 | /** 21 | * Integration test for simple point-to-point messaging. 22 | */ 23 | @SkipIfTlsNotActivated 24 | public class SSLSimpleQueueMessageIT extends AbstractITQueueSSL { 25 | 26 | private static final String QUEUE_NAME = "test.queue."+SSLSimpleQueueMessageIT.class.getCanonicalName(); 27 | private static final long TEST_RECEIVE_TIMEOUT = 1000; // one second 28 | 29 | private void messageTestBase(MessageTestType mtt) throws Exception { 30 | try { 31 | queueConn.start(); 32 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 33 | Queue queue = queueSession.createQueue(QUEUE_NAME); 34 | 35 | drainQueue(queueSession, queue); 36 | 37 | QueueSender queueSender = queueSession.createSender(queue); 38 | queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 39 | queueSender.send(mtt.gen(queueSession, (Serializable)queue)); 40 | } finally { 41 | reconnect(); 42 | } 43 | 44 | queueConn.start(); 45 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 46 | Queue queue = queueSession.createQueue(QUEUE_NAME); 47 | QueueReceiver queueReceiver = queueSession.createReceiver(queue); 48 | mtt.check(queueReceiver.receive(TEST_RECEIVE_TIMEOUT), (Serializable)queue); 49 | } 50 | 51 | @Test 52 | public void testSendBrowseAndReceiveLongTextMessage() throws Exception { 53 | messageTestBase(MessageTestType.LONG_TEXT); 54 | } 55 | 56 | @Test 57 | public void testSendBrowseAndReceiveTextMessage() throws Exception { 58 | messageTestBase(MessageTestType.TEXT); 59 | } 60 | 61 | @Test 62 | public void testSendBrowseAndReceiveBytesMessage() throws Exception { 63 | messageTestBase(MessageTestType.BYTES); 64 | } 65 | 66 | @Test 67 | public void testSendBrowseAndReceiveMapMessage() throws Exception { 68 | messageTestBase(MessageTestType.MAP); 69 | } 70 | 71 | @Test 72 | public void testSendBrowseAndReceiveStreamMessage() throws Exception { 73 | messageTestBase(MessageTestType.STREAM); 74 | } 75 | 76 | @Test 77 | public void testSendBrowseAndReceiveObjectMessage() throws Exception { 78 | messageTestBase(MessageTestType.OBJECT); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/SimpleQueueMessageIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import java.io.Serializable; 9 | 10 | import jakarta.jms.DeliveryMode; 11 | import jakarta.jms.Queue; 12 | import jakarta.jms.QueueReceiver; 13 | import jakarta.jms.QueueSender; 14 | import jakarta.jms.QueueSession; 15 | import jakarta.jms.Session; 16 | 17 | import org.junit.jupiter.api.Test; 18 | 19 | /** 20 | * Integration test for simple browsing of a queue. 21 | */ 22 | public class SimpleQueueMessageIT extends AbstractITQueue { 23 | 24 | private static final String QUEUE_NAME = "test.queue."+SimpleQueueMessageIT.class.getCanonicalName(); 25 | private static final long TEST_RECEIVE_TIMEOUT = 1000; // one second 26 | 27 | private void messageTestBase(MessageTestType mtt) throws Exception { 28 | try { 29 | queueConn.start(); 30 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 31 | Queue queue = queueSession.createQueue(QUEUE_NAME); 32 | 33 | drainQueue(queueSession, queue); 34 | 35 | QueueSender queueSender = queueSession.createSender(queue); 36 | queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 37 | queueSender.send(mtt.gen(queueSession, (Serializable)queue)); 38 | } finally { 39 | reconnect(); 40 | } 41 | 42 | queueConn.start(); 43 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 44 | Queue queue = queueSession.createQueue(QUEUE_NAME); 45 | QueueReceiver queueReceiver = queueSession.createReceiver(queue); 46 | mtt.check(queueReceiver.receive(TEST_RECEIVE_TIMEOUT), (Serializable)queue); 47 | } 48 | 49 | @Test 50 | public void testSendAndReceiveLongTextMessage() throws Exception { 51 | messageTestBase(MessageTestType.LONG_TEXT); 52 | } 53 | 54 | @Test 55 | public void testSendAndReceiveTextMessage() throws Exception { 56 | messageTestBase(MessageTestType.TEXT); 57 | } 58 | 59 | @Test 60 | public void testSendAndReceiveBytesMessage() throws Exception { 61 | messageTestBase(MessageTestType.BYTES); 62 | } 63 | 64 | @Test 65 | public void testSendAndReceiveLongBytesMessage() throws Exception { 66 | messageTestBase(MessageTestType.LONG_BYTES); 67 | } 68 | 69 | @Test 70 | public void testSendAndReceiveMapMessage() throws Exception { 71 | messageTestBase(MessageTestType.MAP); 72 | } 73 | 74 | @Test 75 | public void testSendAndReceiveStreamMessage() throws Exception { 76 | messageTestBase(MessageTestType.STREAM); 77 | } 78 | 79 | @Test 80 | public void testSendAndReceiveObjectMessage() throws Exception { 81 | messageTestBase(MessageTestType.OBJECT); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/BrowsingMessageQueue.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2014-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import static com.rabbitmq.jms.client.Subscription.JMS_TYPE_IDENTS; 9 | 10 | import java.util.Enumeration; 11 | 12 | import jakarta.jms.JMSException; 13 | import jakarta.jms.QueueBrowser; 14 | 15 | import com.rabbitmq.client.Channel; 16 | import com.rabbitmq.jms.admin.RMQDestination; 17 | import com.rabbitmq.jms.parse.sql.SqlEvaluator; 18 | import com.rabbitmq.jms.parse.sql.SqlParser; 19 | import com.rabbitmq.jms.parse.sql.SqlTokenStream; 20 | import com.rabbitmq.jms.util.RMQJMSSelectorException; 21 | 22 | /** 23 | * Implementation class suitable for storing message information for browsing. 24 | */ 25 | class BrowsingMessageQueue implements QueueBrowser { 26 | 27 | private final String selector; 28 | private final RMQDestination dest; // only needed for getQueue(); 29 | private final SqlEvaluator evaluator; 30 | private final RMQSession session; 31 | private final int queueBrowserReadMax; 32 | private final ReceivingContextConsumer receivingContextConsumer; 33 | 34 | public BrowsingMessageQueue(RMQSession session, RMQDestination dest, String selector, 35 | int queueBrowserReadMax, ReceivingContextConsumer receivingContextConsumer) throws JMSException { 36 | this.dest = dest; 37 | this.selector = selector; 38 | this.session = session; 39 | this.evaluator = setEvaluator(selector); 40 | this.queueBrowserReadMax = queueBrowserReadMax; 41 | this.receivingContextConsumer = receivingContextConsumer; 42 | } 43 | 44 | private static final SqlEvaluator setEvaluator(String selector) throws JMSException { 45 | if (selector==null || selector.trim().isEmpty()) return null; 46 | SqlEvaluator evaluator = new SqlEvaluator(new SqlParser(new SqlTokenStream(selector)), JMS_TYPE_IDENTS); 47 | if (!evaluator.evaluatorOk()) 48 | throw new RMQJMSSelectorException(evaluator.getErrorMessage()); 49 | return evaluator; 50 | } 51 | @Override 52 | public jakarta.jms.Queue getQueue() throws JMSException { 53 | return this.dest; 54 | } 55 | 56 | @Override 57 | public String getMessageSelector() throws JMSException { 58 | return this.selector; 59 | } 60 | 61 | @Override 62 | @SuppressWarnings("rawtypes") 63 | public Enumeration getEnumeration() throws JMSException { 64 | Channel chan = this.session.getBrowsingChannel(); 65 | Enumeration e = new BrowsingMessageEnumeration(this.session, this.dest, chan, this.evaluator, 66 | this.queueBrowserReadMax, this.receivingContextConsumer); 67 | session.closeBrowsingChannel(chan); // this should requeue all the messages browsed 68 | return e; 69 | } 70 | 71 | @Override 72 | public void close() throws JMSException { 73 | //no-op 74 | return; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/DelayedReceiver.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import com.rabbitmq.client.GetResponse; 14 | import com.rabbitmq.jms.util.TimeTracker; 15 | 16 | /** 17 | * Receive messages from RMQ Queue with delay (timer). 18 | *

19 | * The blocking method get() only returns with null when either the Receiver is closed, 20 | * or the timeout expires. 21 | *

22 | */ 23 | class DelayedReceiver { 24 | 25 | private final Logger logger = LoggerFactory.getLogger(DelayedReceiver.class); 26 | 27 | private static final TimeTracker POLLING_INTERVAL = new TimeTracker(100, TimeUnit.MILLISECONDS); // one tenth of a second 28 | 29 | @SuppressWarnings("unused") 30 | private final int batchingSize; 31 | private final RMQMessageConsumer rmqMessageConsumer; 32 | 33 | private final Object responseLock = new Object(); 34 | private boolean aborted = false; // @GuardedBy(responseLock) 35 | 36 | /** 37 | * @param batchingSize - the intended limit of messages that can be pre-fetched. 38 | * @param rmqMessageConsumer - the JMS MessageConsumer we are serving. 39 | */ 40 | public DelayedReceiver(int batchingSize, RMQMessageConsumer rmqMessageConsumer) { 41 | this.batchingSize = batchingSize; 42 | this.rmqMessageConsumer = rmqMessageConsumer; 43 | } 44 | 45 | /** 46 | * Get a message; if there isn't one, try again at intervals not exceeding the total time available. Aborts if closed while polling. 47 | * @param tt - keeps track of the time available 48 | * @return message gotten, or null if timeout or connection closed. 49 | */ 50 | public GetResponse get(TimeTracker tt) { 51 | try { 52 | synchronized (this.responseLock) { 53 | GetResponse resp = this.rmqMessageConsumer.getFromRabbitQueue(); 54 | if (resp != null) return resp; 55 | while (!this.aborted && !tt.timedOut()) { 56 | resp = this.rmqMessageConsumer.getFromRabbitQueue(); 57 | if (resp != null) 58 | break; 59 | new TimeTracker(POLLING_INTERVAL).timedWait(this.responseLock); 60 | } 61 | return resp; 62 | } 63 | 64 | } catch (InterruptedException e) { 65 | logger.warn("Get interrupted while buffer.poll-ing.", e); 66 | Thread.currentThread().interrupt(); 67 | return null; 68 | } 69 | } 70 | 71 | private void abort() { 72 | synchronized(this.responseLock) { 73 | this.aborted = true; 74 | this.responseLock.notifyAll(); 75 | } 76 | } 77 | 78 | public void close() { 79 | this.abort(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/AbstractITQueue.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import static org.junit.jupiter.api.Assertions.fail; 9 | 10 | import com.rabbitmq.TestUtils; 11 | import com.rabbitmq.client.Channel; 12 | import com.rabbitmq.client.Connection; 13 | import com.rabbitmq.jms.client.RMQConnection; 14 | import java.io.IOException; 15 | import java.lang.reflect.Field; 16 | import java.util.concurrent.TimeoutException; 17 | import jakarta.jms.Message; 18 | import jakarta.jms.Queue; 19 | import jakarta.jms.QueueConnection; 20 | import jakarta.jms.QueueConnectionFactory; 21 | import jakarta.jms.QueueReceiver; 22 | import jakarta.jms.QueueSession; 23 | 24 | import com.rabbitmq.jms.admin.RMQConnectionFactory; 25 | import org.junit.jupiter.api.AfterEach; 26 | import org.junit.jupiter.api.BeforeEach; 27 | 28 | public abstract class AbstractITQueue { 29 | QueueConnectionFactory connFactory; 30 | protected QueueConnection queueConn; 31 | 32 | @BeforeEach 33 | public void beforeTests() throws Exception { 34 | this.connFactory = (QueueConnectionFactory) AbstractTestConnectionFactory.getTestConnectionFactory() 35 | .getConnectionFactory(); 36 | customise((RMQConnectionFactory) connFactory); 37 | this.queueConn = connFactory.createQueueConnection(); 38 | } 39 | 40 | protected void customise(RMQConnectionFactory connectionFactory) { 41 | 42 | } 43 | 44 | protected static void drainQueue(QueueSession session, Queue queue) throws Exception { 45 | QueueReceiver receiver = session.createReceiver(queue); 46 | int n=0; 47 | Message msg = receiver.receiveNoWait(); 48 | while (msg != null) { 49 | ++n; 50 | msg = receiver.receiveNoWait(); 51 | } 52 | if (n > 0) { 53 | System.out.println(">> INFO >> Drained messages (n=" + n + ") from queue " + queue + " prior to test."); 54 | } 55 | } 56 | 57 | protected void reconnect() throws Exception { 58 | if (queueConn != null) { 59 | this.queueConn.close(); 60 | this.queueConn = connFactory.createQueueConnection(); 61 | } else { 62 | fail("Cannot reconnect"); 63 | } 64 | } 65 | 66 | @AfterEach 67 | public void afterTests() throws Exception { 68 | if (queueConn != null) 69 | queueConn.close(); 70 | } 71 | 72 | protected Connection amqpConnection() { 73 | Connection amqpConnection = null; 74 | if (this.queueConn != null) { 75 | amqpConnection = TestUtils.amqpConnection(this.queueConn); 76 | } 77 | return amqpConnection; 78 | } 79 | 80 | protected void deleteQueue(String queue) { 81 | try (Channel ch = amqpConnection().createChannel()) { 82 | ch.queueDelete(queue); 83 | } catch (IOException | TimeoutException e) { 84 | throw new RuntimeException(e); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/NamingStrategyIT.java: -------------------------------------------------------------------------------- 1 | package com.rabbitmq.integration.tests; 2 | 3 | import static com.rabbitmq.TestUtils.name; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import com.rabbitmq.TestUtils; 7 | import com.rabbitmq.client.Channel; 8 | import com.rabbitmq.jms.admin.NamingStrategy; 9 | import com.rabbitmq.jms.admin.RMQConnectionFactory; 10 | import jakarta.jms.*; 11 | import java.io.IOException; 12 | import java.lang.reflect.Proxy; 13 | import java.util.Set; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | import org.junit.jupiter.api.AfterEach; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.Test; 18 | import org.junit.jupiter.api.TestInfo; 19 | 20 | public class NamingStrategyIT { 21 | 22 | RMQConnectionFactory cf; 23 | Connection c; 24 | Session s; 25 | Set events = ConcurrentHashMap.newKeySet(); 26 | Channel amqpChannel; 27 | 28 | @BeforeEach 29 | void init() throws Exception { 30 | cf = 31 | (RMQConnectionFactory) 32 | AbstractTestConnectionFactory.getTestConnectionFactory().getConnectionFactory(); 33 | NamingStrategy namingStrategy = 34 | (NamingStrategy) 35 | Proxy.newProxyInstance( 36 | this.getClass().getClassLoader(), 37 | new Class[] {NamingStrategy.class}, 38 | (proxy, method, args) -> { 39 | events.add(method.getName()); 40 | return method.invoke(NamingStrategy.DEFAULT, args); 41 | }); 42 | cf.setNamingStrategy(namingStrategy); 43 | c = cf.createConnection(); 44 | s = c.createSession(); 45 | c.start(); 46 | amqpChannel = TestUtils.amqpConnection(c).createChannel(); 47 | } 48 | 49 | @AfterEach 50 | void tearDown() throws Exception { 51 | amqpChannel.close(); 52 | s.close(); 53 | c.close(); 54 | events.clear(); 55 | } 56 | 57 | @Test 58 | void topicExchangeName(TestInfo info) throws Exception { 59 | assertThat(events).isEmpty(); 60 | String name = name(info); 61 | Topic topic = s.createTopic(name); 62 | assertThat(events).hasSize(1).contains("topicExchangeName"); 63 | MessageConsumer consumer = s.createConsumer(topic); 64 | assertThat(events).hasSize(2).contains("topicSubscriberQueuePrefix"); 65 | consumer.close(); 66 | 67 | s.createConsumer(topic, "boolProp"); 68 | assertThat(events).hasSize(3).contains("nonDurableSubscriberTopicSelectorExchangePrefix"); 69 | 70 | name = name(info); 71 | consumer = s.createDurableSubscriber(topic, name, "boolProp", false); 72 | consumer.close(); 73 | deleteQueue(name); 74 | assertThat(events).hasSize(4).contains("durableSubscriberTopicSelectorExchangePrefix"); 75 | 76 | name = name(info); 77 | s.createQueue(name); 78 | deleteQueue(name); 79 | assertThat(events).hasSize(5).contains("queueExchangeName"); 80 | 81 | s.createTemporaryTopic(); 82 | assertThat(events).hasSize(7).contains("temporaryTopicPrefix", "temporaryTopicExchangeName"); 83 | 84 | s.createTemporaryQueue(); 85 | assertThat(events).hasSize(9).contains("temporaryQueuePrefix", "temporaryQueueExchangeName"); 86 | } 87 | 88 | void deleteQueue(String name) throws IOException { 89 | this.amqpChannel.queueDelete(name); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/RabbitMQRedeliverOnNackIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | import jakarta.jms.DeliveryMode; 13 | import jakarta.jms.Queue; 14 | import jakarta.jms.QueueReceiver; 15 | import jakarta.jms.QueueSender; 16 | import jakarta.jms.QueueSession; 17 | import jakarta.jms.Session; 18 | import jakarta.jms.TextMessage; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | 23 | /** 24 | * Integration test for re-deliver on rollback. 25 | */ 26 | public class RabbitMQRedeliverOnNackIT extends AbstractITQueue { 27 | private static final String QUEUE_NAME = "test.queue."+RabbitMQRedeliverOnNackIT.class.getCanonicalName(); 28 | private static final String MESSAGE = "Hello " + RabbitMQRedeliverOnNackIT.class.getName(); 29 | private static final long TEST_RECEIVE_TIMEOUT = 1000; // one second 30 | 31 | /** 32 | * Test that Rabbit re-delivers a received message which has been rolled-back. 33 | * @throws Exception on test failure 34 | */ 35 | @Test 36 | public void testRMQRedeliverOnNack() throws Exception { 37 | try { 38 | queueConn.start(); 39 | QueueSession queueSession = queueConn.createQueueSession( false /*not transacted*/ 40 | , Session.CLIENT_ACKNOWLEDGE); 41 | Queue queue = queueSession.createQueue(QUEUE_NAME); 42 | QueueSender queueSender = queueSession.createSender(queue); 43 | queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 44 | TextMessage message = queueSession.createTextMessage(MESSAGE); 45 | queueSender.send(message); 46 | } finally { 47 | reconnect(); 48 | } 49 | 50 | queueConn.start(); 51 | QueueSession queueSession = queueConn.createQueueSession( true /*transacted*/ 52 | , Session.DUPS_OK_ACKNOWLEDGE); 53 | Queue queue = queueSession.createQueue(QUEUE_NAME); 54 | QueueReceiver queueReceiver = queueSession.createReceiver(queue); 55 | TextMessage message = (TextMessage) queueReceiver.receive(TEST_RECEIVE_TIMEOUT); 56 | assertNotNull(message, "No message delivered initially"); 57 | assertEquals(MESSAGE, message.getText(), "Wrong message delivered initially"); 58 | 59 | queueSession.rollback(); 60 | 61 | message = (TextMessage) queueReceiver.receive(TEST_RECEIVE_TIMEOUT); 62 | 63 | assertNotNull(message, "No message delivered after rollback"); 64 | assertEquals(MESSAGE, message.getText(), "Wrong message redelivered"); 65 | assertTrue(message.getJMSRedelivered(), "Message not marked 'redelivered'"); 66 | 67 | queueSession.commit(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/TemporaryTopicIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | 11 | import jakarta.jms.DeliveryMode; 12 | import jakarta.jms.Session; 13 | import jakarta.jms.TextMessage; 14 | import jakarta.jms.Topic; 15 | import jakarta.jms.TopicPublisher; 16 | import jakarta.jms.TopicSession; 17 | import jakarta.jms.TopicSubscriber; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | /** 22 | * Integration test 23 | */ 24 | public class TemporaryTopicIT extends AbstractITTopic { 25 | 26 | private static final String MESSAGE = "Hello " + TemporaryTopicIT.class.getName(); 27 | 28 | @Test 29 | public void testTopicSendAndReceiveSingleSession() throws Exception { 30 | topicConn.start(); 31 | TopicSession topicSession = topicConn.createTopicSession(false, Session.DUPS_OK_ACKNOWLEDGE); 32 | Topic topic = topicSession.createTemporaryTopic(); 33 | TopicPublisher topicSender = topicSession.createPublisher(topic); 34 | topicSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 35 | 36 | TopicSubscriber topicReceiver1 = topicSession.createSubscriber(topic); 37 | TopicSubscriber topicReceiver2 = topicSession.createSubscriber(topic); 38 | 39 | TextMessage message = topicSession.createTextMessage(MESSAGE); 40 | topicSender.send(message); 41 | 42 | TextMessage message1 = (TextMessage) topicReceiver1.receive(1000); 43 | TextMessage message2 = (TextMessage) topicReceiver2.receive(1000); 44 | assertNotNull(message1); 45 | assertNotNull(message2); 46 | assertEquals(MESSAGE, message1.getText()); 47 | assertEquals(MESSAGE, message2.getText()); 48 | } 49 | 50 | @Test 51 | public void testTopicSendAndReceiveTwoSessions() throws Exception { 52 | topicConn.start(); 53 | TopicSession topicSession1 = topicConn.createTopicSession(false, Session.DUPS_OK_ACKNOWLEDGE); 54 | TopicSession topicSession2 = topicConn.createTopicSession(false, Session.DUPS_OK_ACKNOWLEDGE); 55 | Topic topic = topicSession1.createTemporaryTopic(); 56 | TopicPublisher topicSender = topicSession1.createPublisher(topic); 57 | topicSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 58 | 59 | TopicSubscriber topicReceiver1 = topicSession1.createSubscriber(topic); 60 | TopicSubscriber topicReceiver2 = topicSession2.createSubscriber(topic); 61 | 62 | TextMessage message = topicSession1.createTextMessage(MESSAGE); 63 | topicSender.send(message); 64 | 65 | TextMessage message1 = (TextMessage) topicReceiver1.receive(1000); 66 | TextMessage message2 = (TextMessage) topicReceiver2.receive(1000); 67 | assertNotNull(message1); 68 | assertNotNull(message2); 69 | assertEquals(MESSAGE, message1.getText()); 70 | assertEquals(MESSAGE, message2.getText()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/SSLHostnameVerificationIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertThrows; 9 | 10 | import com.rabbitmq.TestUtils.SkipIfTlsNotActivated; 11 | import com.rabbitmq.jms.admin.RMQConnectionFactory; 12 | import java.io.FileInputStream; 13 | import java.security.KeyStore; 14 | import java.security.cert.CertificateFactory; 15 | import java.security.cert.X509Certificate; 16 | import jakarta.jms.Connection; 17 | import jakarta.jms.JMSException; 18 | import jakarta.jms.JMSSecurityException; 19 | import javax.net.ssl.SSLContext; 20 | import javax.net.ssl.TrustManagerFactory; 21 | import org.junit.jupiter.api.BeforeAll; 22 | import org.junit.jupiter.api.BeforeEach; 23 | import org.junit.jupiter.api.Test; 24 | import org.junit.jupiter.api.condition.EnabledForJreRange; 25 | import org.junit.jupiter.api.condition.JRE; 26 | 27 | /** Integration test for hostname verification with TLS. */ 28 | @SkipIfTlsNotActivated 29 | @EnabledForJreRange(min = JRE.JAVA_11) 30 | public class SSLHostnameVerificationIT { 31 | 32 | static SSLContext sslContext; 33 | RMQConnectionFactory cf; 34 | 35 | @BeforeAll 36 | static void initCrypto() throws Exception { 37 | sslContext = SSLContext.getInstance("TLSv1.2"); 38 | 39 | KeyStore ks = KeyStore.getInstance("JKS"); 40 | ks.load(null, null); 41 | ks.setCertificateEntry("default", caCertificate()); 42 | TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); 43 | tmf.init(ks); 44 | sslContext.init(null, tmf.getTrustManagers(), null); 45 | } 46 | 47 | static X509Certificate caCertificate() throws Exception { 48 | return loadCertificate(caCertificateFile()); 49 | } 50 | 51 | static String caCertificateFile() { 52 | return System.getProperty("test-tls-certs.dir", "tls-gen/basic") + "/testca/cacert.pem"; 53 | } 54 | 55 | static X509Certificate loadCertificate(String file) throws Exception { 56 | try (FileInputStream inputStream = new FileInputStream(file)) { 57 | CertificateFactory fact = CertificateFactory.getInstance("X.509"); 58 | X509Certificate certificate = (X509Certificate) fact.generateCertificate(inputStream); 59 | return certificate; 60 | } 61 | } 62 | 63 | @BeforeEach 64 | public void init() { 65 | cf = new RMQConnectionFactory(); 66 | cf.useSslProtocol(sslContext); 67 | cf.setHostnameVerification(true); 68 | } 69 | 70 | @Test 71 | public void hostnameVerificationEnabledShouldPassForLocalhost() throws JMSException { 72 | cf.setHost("localhost"); 73 | Connection connection = null; 74 | try { 75 | connection = cf.createConnection(); 76 | } finally { 77 | if (connection != null) { 78 | connection.close(); 79 | } 80 | } 81 | } 82 | 83 | @Test 84 | public void hostnameVerificationEnabledShouldFailForLoopbackInterface() throws JMSException { 85 | cf.setHost("127.0.0.1"); 86 | assertThrows(JMSSecurityException.class, () -> cf.createConnection()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/RMQConnectionMetaData.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import java.util.Enumeration; 9 | 10 | import jakarta.jms.ConnectionMetaData; 11 | import jakarta.jms.JMSException; 12 | 13 | /** 14 | * Meta data for {@link RMQConnection} 15 | */ 16 | public class RMQConnectionMetaData implements ConnectionMetaData { 17 | 18 | JMSMetaData jmsMetaData; 19 | 20 | public RMQConnectionMetaData() { 21 | this.jmsMetaData = new JMSMetaData(); 22 | } 23 | 24 | private static final GenericVersion RABBITMQ_VERSION_OBJECT = 25 | new GenericVersion(com.rabbitmq.client.impl.Version.class.getPackage().getImplementationVersion()); 26 | private static final String RABBITMQ_VERSION = RABBITMQ_VERSION_OBJECT.toString(); 27 | private static final int RABBITMQ_MAJOR_VERSION = RABBITMQ_VERSION_OBJECT.getMajor(); 28 | private static final int RABBITMQ_MINOR_VERSION = RABBITMQ_VERSION_OBJECT.getMinor(); 29 | 30 | 31 | /** 32 | * These two are currently not used, they are needed for a JMSCTS test 33 | * We need to make sure these properties get into the messages 34 | */ 35 | public static final String JMSX_GROUP_ID_LABEL = "JMSXGroupID"; // value should be VMW 36 | public static final String JMSX_GROUP_SEQ_LABEL = "JMSXGroupSeq"; 37 | 38 | /** 39 | * {@inheritDoc} 40 | */ 41 | @Override 42 | public String getJMSVersion() throws JMSException { 43 | return jmsMetaData.getJMSVersion(); 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | @Override 50 | public int getJMSMajorVersion() throws JMSException { 51 | return jmsMetaData.getJMSMajorVersion(); 52 | } 53 | 54 | /** 55 | * {@inheritDoc} 56 | */ 57 | @Override 58 | public int getJMSMinorVersion() throws JMSException { 59 | return jmsMetaData.getJMSMinorVersion(); 60 | } 61 | 62 | /** 63 | * {@inheritDoc} 64 | */ 65 | @Override 66 | public String getJMSProviderName() throws JMSException { 67 | return jmsMetaData.getJMSProviderName(); 68 | } 69 | 70 | /** 71 | * {@inheritDoc} 72 | */ 73 | @Override 74 | public String getProviderVersion() throws JMSException { 75 | return RABBITMQ_VERSION; 76 | } 77 | 78 | /** 79 | * {@inheritDoc} 80 | */ 81 | @Override 82 | public int getProviderMajorVersion() throws JMSException { 83 | return RABBITMQ_MAJOR_VERSION; 84 | } 85 | 86 | /** 87 | * {@inheritDoc} 88 | */ 89 | @Override 90 | public int getProviderMinorVersion() throws JMSException { 91 | return RABBITMQ_MINOR_VERSION; 92 | } 93 | 94 | /** 95 | * {@inheritDoc} 96 | * This method currently returns an empty enumeration 97 | */ 98 | @Override 99 | public Enumeration getJMSXPropertyNames() throws JMSException { 100 | return new JMSMetaData.JmsXEnumerator(); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/SimpleSendReceiveTransactionIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNull; 10 | 11 | import jakarta.jms.DeliveryMode; 12 | import jakarta.jms.Queue; 13 | import jakarta.jms.QueueReceiver; 14 | import jakarta.jms.QueueSender; 15 | import jakarta.jms.QueueSession; 16 | import jakarta.jms.Session; 17 | import jakarta.jms.TextMessage; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | /** 22 | * Integration test 23 | */ 24 | public class SimpleSendReceiveTransactionIT extends AbstractITQueue { 25 | private static final String QUEUE_NAME = "test.queue." + SimpleSendReceiveTransactionIT.class.getCanonicalName(); 26 | private static final String MESSAGE = "Hello " + SimpleQueueMessageIT.class.getName(); 27 | 28 | @Test 29 | public void testQueueSendAndRollback() throws Exception { 30 | try { 31 | queueConn.start(); 32 | QueueSession queueSession = queueConn.createQueueSession(true, Session.DUPS_OK_ACKNOWLEDGE); 33 | Queue queue = queueSession.createQueue(QUEUE_NAME); 34 | QueueSender queueSender = queueSession.createSender(queue); 35 | queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 36 | TextMessage message = queueSession.createTextMessage(MESSAGE); 37 | queueSender.send(message); 38 | queueSession.rollback(); 39 | } finally { 40 | reconnect(); 41 | } 42 | queueConn.start(); 43 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 44 | Queue queue = queueSession.createQueue(QUEUE_NAME); 45 | QueueReceiver queueReceiver = queueSession.createReceiver(queue); 46 | TextMessage message = (TextMessage) queueReceiver.receiveNoWait(); 47 | assertNull(message); 48 | } 49 | 50 | @Test 51 | public void testSendAndCommitAndReceiveMessage() throws Exception { 52 | try { 53 | queueConn.start(); 54 | QueueSession queueSession = queueConn.createQueueSession(true, Session.DUPS_OK_ACKNOWLEDGE); 55 | Queue queue = queueSession.createQueue(QUEUE_NAME); 56 | QueueSender queueSender = queueSession.createSender(queue); 57 | queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 58 | TextMessage message = queueSession.createTextMessage(MESSAGE); 59 | queueSender.send(message); 60 | queueSession.commit(); 61 | } finally { 62 | reconnect(); 63 | } 64 | queueConn.start(); 65 | QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE); 66 | Queue queue = queueSession.createQueue(QUEUE_NAME); 67 | QueueReceiver queueReceiver = queueSession.createReceiver(queue); 68 | TextMessage message = (TextMessage) queueReceiver.receive(); 69 | assertEquals(MESSAGE, message.getText()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/BrowsingConsumer.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2014-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.client; 7 | 8 | import java.io.IOException; 9 | import java.util.concurrent.CountDownLatch; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import jakarta.jms.JMSException; 13 | 14 | import com.rabbitmq.client.AMQP; 15 | import com.rabbitmq.client.Channel; 16 | import com.rabbitmq.client.DefaultConsumer; 17 | import com.rabbitmq.client.Envelope; 18 | import com.rabbitmq.client.GetResponse; 19 | import com.rabbitmq.jms.admin.RMQDestination; 20 | import com.rabbitmq.jms.parse.sql.SqlEvaluator; 21 | 22 | class BrowsingConsumer extends DefaultConsumer { 23 | 24 | private final CountDownLatch latch = new CountDownLatch(1); 25 | 26 | private int messagesExpected; 27 | private final java.util.Queue msgQueue; 28 | private final SqlEvaluator evaluator; 29 | private final RMQSession session; 30 | private final RMQDestination dest; 31 | 32 | private final ReceivingContextConsumer receivingContextConsumer; 33 | 34 | public BrowsingConsumer(Channel channel, RMQSession session, RMQDestination dest, int messagesExpected, java.util.Queue msgQueue, SqlEvaluator evaluator, 35 | ReceivingContextConsumer receivingContextConsumer) { 36 | super(channel); 37 | this.messagesExpected = messagesExpected; 38 | this.msgQueue = msgQueue; 39 | this.evaluator = evaluator; 40 | this.session = session; 41 | this.dest = dest; 42 | this.receivingContextConsumer = receivingContextConsumer; 43 | } 44 | 45 | public boolean finishesInTime(int browsingConsumerTimeout) { 46 | try { 47 | return this.latch.await(browsingConsumerTimeout, TimeUnit.MILLISECONDS); 48 | } catch (InterruptedException e) { 49 | Thread.currentThread().interrupt(); // reset interrupted status 50 | return false; 51 | } 52 | } 53 | 54 | @Override 55 | public void handleCancelOk(String consumerTag) { 56 | this.latch.countDown(); 57 | } 58 | 59 | @Override 60 | public void handleCancel(String consumerTag) { 61 | this.latch.countDown(); 62 | } 63 | 64 | @Override 65 | public void handleDelivery(String consumerTag, 66 | Envelope envelope, 67 | AMQP.BasicProperties properties, 68 | byte[] body) 69 | throws IOException { 70 | if (this.messagesExpected==0) return; 71 | try { 72 | RMQMessage msg = RMQMessage.convertMessage(this.session, this.dest, 73 | new GetResponse(envelope, properties, body, --this.messagesExpected), this.receivingContextConsumer); 74 | if (evaluator==null || evaluator.evaluate(msg.toHeaders())) 75 | this.msgQueue.add(msg); 76 | } catch (JMSException e) { 77 | throw new IOException("Failure to convert message to JMS Message type.", e); 78 | } 79 | if (this.messagesExpected == 0) { 80 | this.getChannel().basicCancel(consumerTag); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/jms/util/TestTimeTracker.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.util; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertFalse; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import org.junit.jupiter.api.Test; 15 | 16 | public class TestTimeTracker { 17 | 18 | private static final long QUANTUM_TIME_NANOS = 1000000L; // one millisecond 19 | private static final long SHORT_WAIT_MILLIS = 100; // hundred milliseconds 20 | 21 | /** 22 | * Test TimeTracker constructors. 23 | */ 24 | @Test 25 | public void testTimeTrackerZeros() throws Exception { 26 | assertZero(TimeTracker.ZERO, "ZERO"); 27 | assertZero(new TimeTracker(0, TimeUnit.SECONDS), "(0s)"); 28 | assertZero(new TimeTracker(0, TimeUnit.MILLISECONDS), "(0ms)"); 29 | assertZero(new TimeTracker(0, TimeUnit.NANOSECONDS), "(0nanos)"); 30 | } 31 | 32 | /** 33 | * Test TimeTracker constructors. 34 | */ 35 | @Test 36 | public void testTimeTrackerSmallTimers() throws Exception { 37 | TimeTracker tt = new TimeTracker(SHORT_WAIT_MILLIS, TimeUnit.MILLISECONDS); 38 | assertNotTimedOut(tt, "SHORT_WAIT"); 39 | Thread.sleep(SHORT_WAIT_MILLIS); // we wait for this time, and we should have timed out by now. 40 | assertTimedOut(tt, "SHORT_WAIT"); 41 | } 42 | 43 | private void assertNotTimedOut(TimeTracker tt, String description) { 44 | assertFalse(tt.timedOut(), "TimeTracker "+description+" timed out!"); 45 | assertFalse(0L >= tt.remainingMillis(), "TimeTracker "+description+" run out!"); 46 | } 47 | 48 | private void assertZero(TimeTracker tt, String description) throws Exception { 49 | assertEquals(0L, tt.timeoutNanos(), "TimeTracker "+description+" not a 0L timeout."); 50 | assertTimedOut(tt, description); 51 | } 52 | 53 | private void assertTimedOut(TimeTracker tt, String description) throws Exception { 54 | assertTimedOutOnce(tt, description); 55 | Thread.sleep(SHORT_WAIT_MILLIS); 56 | assertTimedOutOnce(tt, description); // ensure idempotent 57 | } 58 | 59 | private void assertTimedOutOnce(TimeTracker tt, String description) { 60 | assertTrue(tt.timedOut(), "TimeTracker "+description+" not timed out!"); 61 | assertEquals(0l, tt.remainingMillis(), "TimeTracker "+description+" not zero remaining Millis!"); 62 | assertEquals(0l, tt.remainingNanos(), "TimeTracker "+description+" not zero remaining Nanos!"); 63 | Object lock = new Object(); 64 | long startNanos = System.nanoTime(); 65 | synchronized(lock) { 66 | try { 67 | tt.timedWait(lock); 68 | } catch (InterruptedException ie) { 69 | //do nothing 70 | } 71 | } 72 | long intervalNanos = System.nanoTime() - startNanos; 73 | assertTrue(intervalNanos 12 | * These are the tree-build actions associated with an alternative of a production 13 | * in {@link SqlProduction} and must be read along with the definitions in {@link SqlProduction}. 14 | *

15 | * @see SqlProduction 16 | */ 17 | enum SqlTreeType { 18 | LEAF {@Override SqlParseTree tree(SqlParseTree[] sub) { return sub[0]; } } , 19 | DISJUNCTION {@Override SqlParseTree tree(SqlParseTree[] sub) { return new SqlParseTree(this.treeNode(), sub[0], sub[2]); } } , 20 | CONJUNCTION {@Override SqlParseTree tree(SqlParseTree[] sub) { return new SqlParseTree(this.treeNode(), sub[0], sub[2]); } } , 21 | POSTFIXUNARYOP {@Override SqlParseTree tree(SqlParseTree[] sub) { return new SqlParseTree(this.treeNode(sub[1].getNode().value()), sub[0]); } } , 22 | PREFIXUNARYOP {@Override SqlParseTree tree(SqlParseTree[] sub) { return new SqlParseTree(this.treeNode(sub[0].getNode().value()), sub[1]); } }, 23 | BINARYOP {@Override SqlParseTree tree(SqlParseTree[] sub) { return new SqlParseTree(this.treeNode(sub[1].getNode().value()), sub[0], sub[2]); } }, 24 | TERNARYOP {@Override SqlParseTree tree(SqlParseTree[] sub) { return new SqlParseTree(this.treeNode(sub[1].getNode().value()), sub[0], sub[2], sub[4]); } }, 25 | PATTERN1 {@Override SqlParseTree tree(SqlParseTree[] sub) { return new SqlParseTree(this.treeNode(), sub[0]); } } , 26 | PATTERN2 {@Override SqlParseTree tree(SqlParseTree[] sub) { return new SqlParseTree(this.treeNode(), sub[0], sub[2]); } } , 27 | LIST {@Override SqlParseTree tree(SqlParseTree[] sub) { return new SqlParseTree(this.treeNode(new SqlToken(SqlTokenType.LIST, newList(sub[0].getNode().value().getString())))); } } , 28 | // the following node types should never appear in the final tree but are parsing artifacts 29 | COLLAPSE1 {@Override SqlParseTree tree(SqlParseTree[] sub) { return sub[0]; } } , 30 | COLLAPSE2 {@Override SqlParseTree tree(SqlParseTree[] sub) { return sub[1]; } } , 31 | JOINLIST {@Override SqlParseTree tree(SqlParseTree[] sub) { return new SqlParseTree(this.treeNode(LIST, new SqlToken(SqlTokenType.LIST, consList(sub[0].getNode().value().getString(), sub[2].getNode().value().getList())))); } } , 32 | ; 33 | 34 | protected SqlTreeNode treeNode() { 35 | return new SqlTreeNode(this); 36 | } 37 | protected SqlTreeNode treeNode(SqlToken value) { 38 | return new SqlTreeNode(this, value); 39 | } 40 | 41 | protected SqlTreeNode treeNode(SqlTreeType type, SqlToken value) { 42 | return new SqlTreeNode(type, value); 43 | } 44 | 45 | /** Construct a tree of this type from the given array of subtrees */ 46 | abstract SqlParseTree tree(SqlParseTree[] sub); 47 | 48 | private static final List newList(String s) { List al = new ArrayList(); al.add(s); return al; } 49 | private static final List consList(String s, List al) { al.add(0,s); return al; } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/jms/parse/sql/SqlTokenStreamTest.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.jms.parse.sql; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import org.junit.jupiter.api.Test; 15 | 16 | public class SqlTokenStreamTest { 17 | 18 | @Test 19 | public void testSeries() { 20 | assertTokenise("1 2.0 2.1e-1 name is null 'hello world''s' 2e2 2.e2 2D 2.0f", 21 | "integer: 1" 22 | , "float: 2.0" 23 | , "float: 0.21" 24 | , "ident: name" 25 | , "is_null" 26 | , "string: 'hello world''s'" 27 | , "float: 200.0" 28 | , "float: 200.0" 29 | , "float: 2.0" 30 | , "float: 2.0"); 31 | } 32 | 33 | @Test 34 | public void testKeywordIdentifier() { 35 | assertTokenise("nothing not" 36 | , "ident: nothing" 37 | , "not"); 38 | } 39 | 40 | @Test 41 | public void testExpression() { 42 | assertTokenise("nothing IS NULL" 43 | , "ident: nothing" 44 | , "is_null"); 45 | } 46 | 47 | @Test 48 | public void testKeywordSequenceSpacing() { 49 | assertTokenise("IS NOT NULL", "not_null"); 50 | assertTokenise("IS\nNOT NULL", "not_null"); 51 | assertTokenise("IS\tNOT NULL", "not_null"); 52 | assertTokenise("IS\fNOT NULL", "not_null"); 53 | assertTokenise("IS\rNOT NULL", "not_null"); 54 | assertTokenise("IS\u000BNOT NULL", "not_null"); 55 | } 56 | 57 | @Test 58 | public void testIdentifierError() { 59 | assertTokeniseFailure(" ~ABC='abc'"); 60 | } 61 | 62 | @Test 63 | public void testSingleQuoteError() { 64 | assertTokeniseFailure("abc'"); 65 | } 66 | 67 | @Test 68 | public void testGeneralSpacing() { 69 | assertTokenise("\n\t\f\r\u000BIS NULLify \n" 70 | , "ident: IS" 71 | , "ident: NULLify"); 72 | } 73 | 74 | private void assertTokenise(String inStr, String ...strs) { 75 | SqlTokenStream stream = new SqlTokenStream(inStr); 76 | assertEquals("", stream.getResidue(), "Residue not empty"); 77 | List listOut = new ArrayList(); 78 | while (stream.moreTokens()) { 79 | SqlToken t = stream.getNext(); 80 | listOut.add(t.toString()); 81 | } 82 | assertEquals(resultList(strs), listOut, "Parse failure"); 83 | } 84 | 85 | private void assertTokeniseFailure(String inStr, String ...strs) { 86 | SqlTokenStream stream = new SqlTokenStream(inStr); 87 | assertNotEquals("", stream.getResidue(), "Residue empty"); 88 | } 89 | 90 | private static List resultList(String ... strs) { 91 | ArrayList res = new ArrayList(); 92 | for (String str : strs) { 93 | res.add(str); 94 | } 95 | return res; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/com/rabbitmq/integration/tests/ReceivingContextConsumerIT.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | package com.rabbitmq.integration.tests; 7 | 8 | import com.rabbitmq.TestUtils; 9 | import com.rabbitmq.jms.admin.RMQConnectionFactory; 10 | import org.assertj.core.api.Assertions; 11 | import org.junit.jupiter.api.AfterEach; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.Test; 14 | 15 | import jakarta.jms.Connection; 16 | import jakarta.jms.Message; 17 | import jakarta.jms.MessageConsumer; 18 | import jakarta.jms.MessageProducer; 19 | import jakarta.jms.Queue; 20 | import jakarta.jms.Session; 21 | import jakarta.jms.TextMessage; 22 | import java.time.Duration; 23 | import java.util.concurrent.atomic.AtomicInteger; 24 | 25 | import static com.rabbitmq.TestUtils.waitUntil; 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | 28 | /** 29 | * 30 | */ 31 | public class ReceivingContextConsumerIT { 32 | 33 | private static final String QUEUE_NAME = "test.queue." + ReceivingContextConsumerIT.class.getCanonicalName(); 34 | final AtomicInteger receivedCount = new AtomicInteger(0); 35 | Connection connection; 36 | 37 | protected static void drainQueue(Session session, Queue queue) throws Exception { 38 | MessageConsumer receiver = session.createConsumer(queue); 39 | Message msg = receiver.receiveNoWait(); 40 | while (msg != null) { 41 | msg = receiver.receiveNoWait(); 42 | } 43 | } 44 | 45 | @BeforeEach 46 | public void init() throws Exception { 47 | RMQConnectionFactory connectionFactory = (RMQConnectionFactory) AbstractTestConnectionFactory.getTestConnectionFactory() 48 | .getConnectionFactory(); 49 | connectionFactory.setReceivingContextConsumer(ctx -> receivedCount.incrementAndGet()); 50 | connection = connectionFactory.createConnection(); 51 | connection.start(); 52 | } 53 | 54 | @AfterEach 55 | public void tearDown() throws Exception { 56 | if (connection != null) { 57 | connection.close(); 58 | } 59 | com.rabbitmq.client.ConnectionFactory cf = new com.rabbitmq.client.ConnectionFactory(); 60 | try (com.rabbitmq.client.Connection c = cf.newConnection()) { 61 | c.createChannel().queueDelete(QUEUE_NAME); 62 | } 63 | } 64 | 65 | @Test 66 | public void sendingContextConsumerShouldBeCalledWhenSendingMessage() throws Exception { 67 | Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); 68 | Queue queue = session.createQueue(QUEUE_NAME); 69 | MessageConsumer consumer = session.createConsumer(queue); 70 | consumer.setMessageListener(msg -> { 71 | }); 72 | 73 | int initialCount = receivedCount.get(); 74 | TextMessage message = session.createTextMessage("hello"); 75 | MessageProducer producer = session.createProducer(queue); 76 | producer.send(message); 77 | assertThat(waitUntil(Duration.ofSeconds(5), () -> receivedCount.get() == initialCount + 1)).isTrue(); 78 | producer.send(message); 79 | assertThat(waitUntil(Duration.ofSeconds(5), () -> receivedCount.get() == initialCount + 2)).isTrue(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/rabbitmq/jms/client/DelayedMessageService.java: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. 6 | 7 | package com.rabbitmq.jms.client; 8 | 9 | import com.rabbitmq.client.Channel; 10 | import com.rabbitmq.jms.admin.RMQDestination; 11 | 12 | import jakarta.jms.JMSRuntimeException; 13 | import java.io.IOException; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | import java.util.concurrent.Semaphore; 18 | 19 | class DelayedMessageService { 20 | static final String X_DELAYED_JMS_EXCHANGE = "x-delayed-jms-message"; 21 | static final String X_DELAY_HEADER = "x-delay"; 22 | static final String X_DELAYED_JMS_EXCHANGE_HEADER = "delayed-exchange"; 23 | 24 | /** Cache of exchanges which have been bound to the delayed exchange */ 25 | private final Map delayedDestinations; 26 | private volatile boolean delayedExchangeDeclared; 27 | private Semaphore declaring = new Semaphore(1); 28 | 29 | public DelayedMessageService() { 30 | this.delayedDestinations = new ConcurrentHashMap<>(); 31 | } 32 | void close() { 33 | delayedDestinations.clear(); 34 | } 35 | 36 | String delayMessage(Channel channel, RMQDestination destination, Map messageHeaders, long deliveryDelayMs) { 37 | if (deliveryDelayMs <= 0L) return destination.getAmqpExchangeName(); 38 | 39 | declareDelayedExchange(channel); 40 | delayedDestinations.computeIfAbsent(destination.getAmqpExchangeName(), destination1 -> { 41 | bindDestinationToDelayedExchange(channel, destination); 42 | return true; 43 | }); 44 | messageHeaders.put(X_DELAY_HEADER, deliveryDelayMs); 45 | messageHeaders.put(X_DELAYED_JMS_EXCHANGE_HEADER, destination.getAmqpExchangeName()); 46 | return X_DELAYED_JMS_EXCHANGE; 47 | } 48 | private void declareDelayedExchange(Channel channel) { 49 | if (delayedExchangeDeclared) { 50 | return; 51 | } 52 | 53 | try { 54 | declaring.acquire(); 55 | if (delayedExchangeDeclared) { 56 | return; 57 | } 58 | 59 | Map args = new HashMap<>(); 60 | args.put("x-delayed-type", "headers"); 61 | channel.exchangeDeclare(X_DELAYED_JMS_EXCHANGE, "x-delayed-message", true, 62 | false, // autoDelete 63 | false, // internal 64 | args); // object properties 65 | delayedExchangeDeclared = true; 66 | } catch (Exception x) { 67 | throw new JMSRuntimeException("Failed to declare exchange " + X_DELAYED_JMS_EXCHANGE, 68 | "", x); 69 | } finally { 70 | declaring.release(); 71 | } 72 | } 73 | private void bindDestinationToDelayedExchange(Channel channel, RMQDestination destination) { 74 | HashMap map = new HashMap<>(); 75 | map.put(X_DELAYED_JMS_EXCHANGE_HEADER, destination.getAmqpExchangeName()); 76 | try { 77 | channel.exchangeBind(destination.getAmqpExchangeName(), X_DELAYED_JMS_EXCHANGE, "", map); 78 | } catch (IOException e) { 79 | throw new RuntimeException(e); 80 | } 81 | } 82 | } 83 | --------------------------------------------------------------------------------