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 |
--------------------------------------------------------------------------------