Skip to content

Commit 6ed749e

Browse files
garyrussellartembilan
authored andcommitted
GH-2439: Fix Bindings with Broker Gen Queue Names
Resolves #2439 To configure a broker-named queue, the `Queue` name is set to an empty String; the name is later populated when the queue is declared. However, if such a queue is used in a `BindingBuilder`, the builder accesses the name before it is populated. When such a queue is used in a binding, retain a copy of the queue object and retrieve its actual name when the binding is declared. Add a unit test for all binding types; also tested with the following, with both queues being bound properly to the exchange. ```java @bean Queue q1() { return QueueBuilder.nonDurable("").build(); } @bean Queue q2() { return QueueBuilder.nonDurable("").build(); } @bean Binding b1(Queue q1, DirectExchange ex) { return BindingBuilder.bind(q1).to(ex).with("foo"); } @bean Binding b2(Queue q2, DirectExchange ex) { return BindingBuilder.bind(q2).to(ex).with("bar"); } @bean DirectExchange ex() { return new DirectExchange("ex"); } ``` **cherry-pick to 2.4.x**
1 parent 61f535b commit 6ed749e

File tree

3 files changed

+192
-18
lines changed

3 files changed

+192
-18
lines changed

spring-amqp/src/main/java/org/springframework/amqp/core/Binding.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.util.Map;
2020

2121
import org.springframework.lang.Nullable;
22+
import org.springframework.util.Assert;
2223

2324
/**
2425
* Simple container collecting information to describe a binding. Takes String destination and exchange names as
@@ -50,26 +51,46 @@ public enum DestinationType {
5051
EXCHANGE;
5152
}
5253

54+
@Nullable
5355
private final String destination;
5456

5557
private final String exchange;
5658

59+
@Nullable
5760
private final String routingKey;
5861

5962
private final DestinationType destinationType;
6063

64+
@Nullable
65+
private final Queue lazyQueue;
66+
6167
public Binding(String destination, DestinationType destinationType, String exchange, String routingKey,
6268
@Nullable Map<String, Object> arguments) {
6369

70+
this(null, destination, destinationType, exchange, routingKey, arguments);
71+
}
72+
73+
public Binding(@Nullable Queue lazyQueue, @Nullable String destination, DestinationType destinationType,
74+
String exchange, @Nullable String routingKey, @Nullable Map<String, Object> arguments) {
75+
6476
super(arguments);
77+
Assert.isTrue(lazyQueue == null || destinationType.equals(DestinationType.QUEUE),
78+
"'lazyQueue' must be null for destination type " + destinationType);
79+
Assert.isTrue(lazyQueue != null || destination != null, "`destination` cannot be null");
80+
this.lazyQueue = lazyQueue;
6581
this.destination = destination;
6682
this.destinationType = destinationType;
6783
this.exchange = exchange;
6884
this.routingKey = routingKey;
6985
}
7086

7187
public String getDestination() {
72-
return this.destination;
88+
if (this.lazyQueue != null) {
89+
return this.lazyQueue.getActualName();
90+
}
91+
else {
92+
return this.destination;
93+
}
7394
}
7495

7596
public DestinationType getDestinationType() {
@@ -81,6 +102,9 @@ public String getExchange() {
81102
}
82103

83104
public String getRoutingKey() {
105+
if (this.routingKey == null && this.lazyQueue != null) {
106+
return this.lazyQueue.getActualName();
107+
}
84108
return this.routingKey;
85109
}
86110

spring-amqp/src/main/java/org/springframework/amqp/core/BindingBuilder.java

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,7 +37,12 @@ private BindingBuilder() {
3737
}
3838

3939
public static DestinationConfigurer bind(Queue queue) {
40-
return new DestinationConfigurer(queue.getName(), DestinationType.QUEUE);
40+
if ("".equals(queue.getName())) {
41+
return new DestinationConfigurer(queue, DestinationType.QUEUE);
42+
}
43+
else {
44+
return new DestinationConfigurer(queue.getName(), DestinationType.QUEUE);
45+
}
4146
}
4247

4348
public static DestinationConfigurer bind(Exchange exchange) {
@@ -61,13 +66,22 @@ public static final class DestinationConfigurer {
6166

6267
protected final DestinationType type; // NOSONAR
6368

69+
protected final Queue queue; // NOSONAR
70+
6471
DestinationConfigurer(String name, DestinationType type) {
72+
this.queue = null;
6573
this.name = name;
6674
this.type = type;
6775
}
6876

77+
DestinationConfigurer(Queue queue, DestinationType type) {
78+
this.queue = queue;
79+
this.name = null;
80+
this.type = type;
81+
}
82+
6983
public Binding to(FanoutExchange exchange) {
70-
return new Binding(this.name, this.type, exchange.getName(), "", new HashMap<String, Object>());
84+
return new Binding(this.queue, this.name, this.type, exchange.getName(), "", new HashMap<String, Object>());
7185
}
7286

7387
public HeadersExchangeMapConfigurer to(HeadersExchange exchange) {
@@ -134,15 +148,17 @@ public final class HeadersExchangeSingleValueBindingCreator {
134148
}
135149

136150
public Binding exists() {
137-
return new Binding(HeadersExchangeMapConfigurer.this.destination.name,
151+
return new Binding(HeadersExchangeMapConfigurer.this.destination.queue,
152+
HeadersExchangeMapConfigurer.this.destination.name,
138153
HeadersExchangeMapConfigurer.this.destination.type,
139154
HeadersExchangeMapConfigurer.this.exchange.getName(), "", createMapForKeys(this.key));
140155
}
141156

142157
public Binding matches(Object value) {
143158
Map<String, Object> map = new HashMap<String, Object>();
144159
map.put(this.key, value);
145-
return new Binding(HeadersExchangeMapConfigurer.this.destination.name,
160+
return new Binding(HeadersExchangeMapConfigurer.this.destination.queue,
161+
HeadersExchangeMapConfigurer.this.destination.name,
146162
HeadersExchangeMapConfigurer.this.destination.type,
147163
HeadersExchangeMapConfigurer.this.exchange.getName(), "", map);
148164
}
@@ -162,7 +178,8 @@ public final class HeadersExchangeKeysBindingCreator {
162178
}
163179

164180
public Binding exist() {
165-
return new Binding(HeadersExchangeMapConfigurer.this.destination.name,
181+
return new Binding(HeadersExchangeMapConfigurer.this.destination.queue,
182+
HeadersExchangeMapConfigurer.this.destination.name,
166183
HeadersExchangeMapConfigurer.this.destination.type,
167184
HeadersExchangeMapConfigurer.this.exchange.getName(), "", this.headerMap);
168185
}
@@ -182,7 +199,8 @@ public final class HeadersExchangeMapBindingCreator {
182199
}
183200

184201
public Binding match() {
185-
return new Binding(HeadersExchangeMapConfigurer.this.destination.name,
202+
return new Binding(HeadersExchangeMapConfigurer.this.destination.queue,
203+
HeadersExchangeMapConfigurer.this.destination.name,
186204
HeadersExchangeMapConfigurer.this.destination.type,
187205
HeadersExchangeMapConfigurer.this.exchange.getName(), "", this.headerMap);
188206
}
@@ -211,13 +229,13 @@ public static final class TopicExchangeRoutingKeyConfigurer extends AbstractRout
211229
}
212230

213231
public Binding with(String routingKey) {
214-
return new Binding(destination.name, destination.type, exchange, routingKey,
232+
return new Binding(destination.queue, destination.name, destination.type, exchange, routingKey,
215233
Collections.<String, Object>emptyMap());
216234
}
217235

218236
public Binding with(Enum<?> routingKeyEnum) {
219-
return new Binding(destination.name, destination.type, exchange, routingKeyEnum.toString(),
220-
Collections.<String, Object>emptyMap());
237+
return new Binding(destination.queue, destination.name, destination.type, exchange,
238+
routingKeyEnum.toString(), Collections.<String, Object>emptyMap());
221239
}
222240
}
223241

@@ -255,12 +273,14 @@ public GenericArgumentsConfigurer(GenericExchangeRoutingKeyConfigurer configurer
255273
}
256274

257275
public Binding and(Map<String, Object> map) {
258-
return new Binding(this.configurer.destination.name, this.configurer.destination.type, this.configurer.exchange,
276+
return new Binding(this.configurer.destination.queue,
277+
this.configurer.destination.name, this.configurer.destination.type, this.configurer.exchange,
259278
this.routingKey, map);
260279
}
261280

262281
public Binding noargs() {
263-
return new Binding(this.configurer.destination.name, this.configurer.destination.type, this.configurer.exchange,
282+
return new Binding(this.configurer.destination.queue,
283+
this.configurer.destination.name, this.configurer.destination.type, this.configurer.exchange,
264284
this.routingKey, Collections.<String, Object>emptyMap());
265285
}
266286

@@ -276,19 +296,20 @@ public static final class DirectExchangeRoutingKeyConfigurer extends AbstractRou
276296
}
277297

278298
public Binding with(String routingKey) {
279-
return new Binding(destination.name, destination.type, exchange, routingKey,
299+
return new Binding(destination.queue, destination.name, destination.type, exchange, routingKey,
280300
Collections.<String, Object>emptyMap());
281301
}
282302

283303
public Binding with(Enum<?> routingKeyEnum) {
284-
return new Binding(destination.name, destination.type, exchange, routingKeyEnum.toString(),
285-
Collections.<String, Object>emptyMap());
304+
return new Binding(destination.queue, destination.name, destination.type, exchange,
305+
routingKeyEnum.toString(), Collections.<String, Object>emptyMap());
286306
}
287307

288308
public Binding withQueueName() {
289-
return new Binding(destination.name, destination.type, exchange, destination.name,
309+
return new Binding(destination.queue, destination.name, destination.type, exchange, destination.name,
290310
Collections.<String, Object>emptyMap());
291311
}
312+
292313
}
293314

294315
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.amqp.core;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import java.util.Collections;
22+
23+
import org.junit.jupiter.api.BeforeAll;
24+
import org.junit.jupiter.api.Test;
25+
26+
/**
27+
* Copy of {@link BindingBuilderTests} but using a queue with a lazy name.
28+
*
29+
* @author Mark Fisher
30+
* @author Artem Yakshin
31+
* @author Gary Russell
32+
*/
33+
public class BindingBuilderWithLazyQueueNameTests {
34+
35+
private static Queue queue;
36+
37+
@BeforeAll
38+
public static void setUp() {
39+
queue = new Queue("");
40+
queue.setActualName("actual");
41+
}
42+
43+
@Test
44+
public void fanoutBinding() {
45+
FanoutExchange fanoutExchange = new FanoutExchange("f");
46+
Binding binding = BindingBuilder.bind(queue).to(fanoutExchange);
47+
assertThat(binding).isNotNull();
48+
assertThat(binding.getExchange()).isEqualTo(fanoutExchange.getName());
49+
assertThat(binding.getRoutingKey()).isEqualTo("");
50+
assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE);
51+
assertThat(binding.getDestination()).isEqualTo(queue.getActualName());
52+
}
53+
54+
@Test
55+
public void directBinding() {
56+
DirectExchange directExchange = new DirectExchange("d");
57+
String routingKey = "r";
58+
Binding binding = BindingBuilder.bind(queue).to(directExchange).with(routingKey);
59+
assertThat(binding).isNotNull();
60+
assertThat(binding.getExchange()).isEqualTo(directExchange.getName());
61+
assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE);
62+
assertThat(binding.getDestination()).isEqualTo(queue.getActualName());
63+
assertThat(binding.getRoutingKey()).isEqualTo(routingKey);
64+
}
65+
66+
@Test
67+
public void directBindingWithQueueName() {
68+
DirectExchange directExchange = new DirectExchange("d");
69+
Binding binding = BindingBuilder.bind(queue).to(directExchange).withQueueName();
70+
assertThat(binding).isNotNull();
71+
assertThat(binding.getExchange()).isEqualTo(directExchange.getName());
72+
assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE);
73+
assertThat(binding.getDestination()).isEqualTo(queue.getActualName());
74+
assertThat(binding.getRoutingKey()).isEqualTo(queue.getActualName());
75+
}
76+
77+
@Test
78+
public void topicBinding() {
79+
TopicExchange topicExchange = new TopicExchange("t");
80+
String routingKey = "r";
81+
Binding binding = BindingBuilder.bind(queue).to(topicExchange).with(routingKey);
82+
assertThat(binding).isNotNull();
83+
assertThat(binding.getExchange()).isEqualTo(topicExchange.getName());
84+
assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE);
85+
assertThat(binding.getDestination()).isEqualTo(queue.getActualName());
86+
assertThat(binding.getRoutingKey()).isEqualTo(routingKey);
87+
}
88+
89+
@Test
90+
public void headerBinding() {
91+
HeadersExchange headersExchange = new HeadersExchange("h");
92+
String headerKey = "headerKey";
93+
Binding binding = BindingBuilder.bind(queue).to(headersExchange).where(headerKey).exists();
94+
assertThat(binding).isNotNull();
95+
assertThat(binding.getExchange()).isEqualTo(headersExchange.getName());
96+
assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE);
97+
assertThat(binding.getDestination()).isEqualTo(queue.getActualName());
98+
assertThat(binding.getRoutingKey()).isEqualTo("");
99+
}
100+
101+
@Test
102+
public void customBinding() {
103+
class CustomExchange extends AbstractExchange {
104+
CustomExchange(String name) {
105+
super(name);
106+
}
107+
108+
@Override
109+
public String getType() {
110+
return "x-custom";
111+
}
112+
}
113+
Object argumentObject = new Object();
114+
CustomExchange customExchange = new CustomExchange("c");
115+
String routingKey = "r";
116+
Binding binding = BindingBuilder.//
117+
bind(queue).//
118+
to(customExchange).//
119+
with(routingKey).//
120+
and(Collections.<String, Object>singletonMap("k", argumentObject));
121+
assertThat(binding).isNotNull();
122+
assertThat(binding.getArguments().get("k")).isEqualTo(argumentObject);
123+
assertThat(binding.getExchange()).isEqualTo(customExchange.getName());
124+
assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE);
125+
assertThat(binding.getDestination()).isEqualTo(queue.getActualName());
126+
assertThat(binding.getRoutingKey()).isEqualTo(routingKey);
127+
}
128+
129+
}

0 commit comments

Comments
 (0)