diff --git a/spring-amqp/src/main/java/org/springframework/amqp/core/Binding.java b/spring-amqp/src/main/java/org/springframework/amqp/core/Binding.java index abaf75e4..a5ee7427 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/core/Binding.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/core/Binding.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package org.springframework.amqp.core; import java.util.Map; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Simple container collecting information to describe a binding. Takes String destination and exchange names as @@ -50,18 +51,33 @@ public class Binding extends AbstractDeclarable { EXCHANGE; } + @Nullable private final String destination; private final String exchange; + @Nullable private final String routingKey; private final DestinationType destinationType; + @Nullable + private final Queue lazyQueue; + public Binding(String destination, DestinationType destinationType, String exchange, String routingKey, @Nullable Map arguments) { + this(null, destination, destinationType, exchange, routingKey, arguments); + } + + public Binding(@Nullable Queue lazyQueue, @Nullable String destination, DestinationType destinationType, + String exchange, @Nullable String routingKey, @Nullable Map arguments) { + super(arguments); + Assert.isTrue(lazyQueue == null || destinationType.equals(DestinationType.QUEUE), + "'lazyQueue' must be null for destination type " + destinationType); + Assert.isTrue(lazyQueue != null || destination != null, "`destination` cannot be null"); + this.lazyQueue = lazyQueue; this.destination = destination; this.destinationType = destinationType; this.exchange = exchange; @@ -69,7 +85,12 @@ public class Binding extends AbstractDeclarable { } public String getDestination() { - return this.destination; + if (this.lazyQueue != null) { + return this.lazyQueue.getActualName(); + } + else { + return this.destination; + } } public DestinationType getDestinationType() { @@ -81,6 +102,9 @@ public class Binding extends AbstractDeclarable { } public String getRoutingKey() { + if (this.routingKey == null && this.lazyQueue != null) { + return this.lazyQueue.getActualName(); + } return this.routingKey; } diff --git a/spring-amqp/src/main/java/org/springframework/amqp/core/BindingBuilder.java b/spring-amqp/src/main/java/org/springframework/amqp/core/BindingBuilder.java index b1105d25..64248175 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/core/BindingBuilder.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/core/BindingBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,12 @@ public final class BindingBuilder { } public static DestinationConfigurer bind(Queue queue) { - return new DestinationConfigurer(queue.getName(), DestinationType.QUEUE); + if ("".equals(queue.getName())) { + return new DestinationConfigurer(queue, DestinationType.QUEUE); + } + else { + return new DestinationConfigurer(queue.getName(), DestinationType.QUEUE); + } } public static DestinationConfigurer bind(Exchange exchange) { @@ -61,13 +66,22 @@ public final class BindingBuilder { protected final DestinationType type; // NOSONAR + protected final Queue queue; // NOSONAR + DestinationConfigurer(String name, DestinationType type) { + this.queue = null; this.name = name; this.type = type; } + DestinationConfigurer(Queue queue, DestinationType type) { + this.queue = queue; + this.name = null; + this.type = type; + } + public Binding to(FanoutExchange exchange) { - return new Binding(this.name, this.type, exchange.getName(), "", new HashMap()); + return new Binding(this.queue, this.name, this.type, exchange.getName(), "", new HashMap()); } public HeadersExchangeMapConfigurer to(HeadersExchange exchange) { @@ -134,7 +148,8 @@ public final class BindingBuilder { } public Binding exists() { - return new Binding(HeadersExchangeMapConfigurer.this.destination.name, + return new Binding(HeadersExchangeMapConfigurer.this.destination.queue, + HeadersExchangeMapConfigurer.this.destination.name, HeadersExchangeMapConfigurer.this.destination.type, HeadersExchangeMapConfigurer.this.exchange.getName(), "", createMapForKeys(this.key)); } @@ -142,7 +157,8 @@ public final class BindingBuilder { public Binding matches(Object value) { Map map = new HashMap(); map.put(this.key, value); - return new Binding(HeadersExchangeMapConfigurer.this.destination.name, + return new Binding(HeadersExchangeMapConfigurer.this.destination.queue, + HeadersExchangeMapConfigurer.this.destination.name, HeadersExchangeMapConfigurer.this.destination.type, HeadersExchangeMapConfigurer.this.exchange.getName(), "", map); } @@ -162,7 +178,8 @@ public final class BindingBuilder { } public Binding exist() { - return new Binding(HeadersExchangeMapConfigurer.this.destination.name, + return new Binding(HeadersExchangeMapConfigurer.this.destination.queue, + HeadersExchangeMapConfigurer.this.destination.name, HeadersExchangeMapConfigurer.this.destination.type, HeadersExchangeMapConfigurer.this.exchange.getName(), "", this.headerMap); } @@ -182,7 +199,8 @@ public final class BindingBuilder { } public Binding match() { - return new Binding(HeadersExchangeMapConfigurer.this.destination.name, + return new Binding(HeadersExchangeMapConfigurer.this.destination.queue, + HeadersExchangeMapConfigurer.this.destination.name, HeadersExchangeMapConfigurer.this.destination.type, HeadersExchangeMapConfigurer.this.exchange.getName(), "", this.headerMap); } @@ -211,13 +229,13 @@ public final class BindingBuilder { } public Binding with(String routingKey) { - return new Binding(destination.name, destination.type, exchange, routingKey, + return new Binding(destination.queue, destination.name, destination.type, exchange, routingKey, Collections.emptyMap()); } public Binding with(Enum routingKeyEnum) { - return new Binding(destination.name, destination.type, exchange, routingKeyEnum.toString(), - Collections.emptyMap()); + return new Binding(destination.queue, destination.name, destination.type, exchange, + routingKeyEnum.toString(), Collections.emptyMap()); } } @@ -255,12 +273,14 @@ public final class BindingBuilder { } public Binding and(Map map) { - return new Binding(this.configurer.destination.name, this.configurer.destination.type, this.configurer.exchange, + return new Binding(this.configurer.destination.queue, + this.configurer.destination.name, this.configurer.destination.type, this.configurer.exchange, this.routingKey, map); } public Binding noargs() { - return new Binding(this.configurer.destination.name, this.configurer.destination.type, this.configurer.exchange, + return new Binding(this.configurer.destination.queue, + this.configurer.destination.name, this.configurer.destination.type, this.configurer.exchange, this.routingKey, Collections.emptyMap()); } @@ -276,19 +296,20 @@ public final class BindingBuilder { } public Binding with(String routingKey) { - return new Binding(destination.name, destination.type, exchange, routingKey, + return new Binding(destination.queue, destination.name, destination.type, exchange, routingKey, Collections.emptyMap()); } public Binding with(Enum routingKeyEnum) { - return new Binding(destination.name, destination.type, exchange, routingKeyEnum.toString(), - Collections.emptyMap()); + return new Binding(destination.queue, destination.name, destination.type, exchange, + routingKeyEnum.toString(), Collections.emptyMap()); } public Binding withQueueName() { - return new Binding(destination.name, destination.type, exchange, destination.name, + return new Binding(destination.queue, destination.name, destination.type, exchange, destination.name, Collections.emptyMap()); } + } } diff --git a/spring-amqp/src/test/java/org/springframework/amqp/core/BindingBuilderWithLazyQueueNameTests.java b/spring-amqp/src/test/java/org/springframework/amqp/core/BindingBuilderWithLazyQueueNameTests.java new file mode 100644 index 00000000..5fe956b5 --- /dev/null +++ b/spring-amqp/src/test/java/org/springframework/amqp/core/BindingBuilderWithLazyQueueNameTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.amqp.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Copy of {@link BindingBuilderTests} but using a queue with a lazy name. + * + * @author Mark Fisher + * @author Artem Yakshin + * @author Gary Russell + */ +public class BindingBuilderWithLazyQueueNameTests { + + private static Queue queue; + + @BeforeAll + public static void setUp() { + queue = new Queue(""); + queue.setActualName("actual"); + } + + @Test + public void fanoutBinding() { + FanoutExchange fanoutExchange = new FanoutExchange("f"); + Binding binding = BindingBuilder.bind(queue).to(fanoutExchange); + assertThat(binding).isNotNull(); + assertThat(binding.getExchange()).isEqualTo(fanoutExchange.getName()); + assertThat(binding.getRoutingKey()).isEqualTo(""); + assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE); + assertThat(binding.getDestination()).isEqualTo(queue.getActualName()); + } + + @Test + public void directBinding() { + DirectExchange directExchange = new DirectExchange("d"); + String routingKey = "r"; + Binding binding = BindingBuilder.bind(queue).to(directExchange).with(routingKey); + assertThat(binding).isNotNull(); + assertThat(binding.getExchange()).isEqualTo(directExchange.getName()); + assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE); + assertThat(binding.getDestination()).isEqualTo(queue.getActualName()); + assertThat(binding.getRoutingKey()).isEqualTo(routingKey); + } + + @Test + public void directBindingWithQueueName() { + DirectExchange directExchange = new DirectExchange("d"); + Binding binding = BindingBuilder.bind(queue).to(directExchange).withQueueName(); + assertThat(binding).isNotNull(); + assertThat(binding.getExchange()).isEqualTo(directExchange.getName()); + assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE); + assertThat(binding.getDestination()).isEqualTo(queue.getActualName()); + assertThat(binding.getRoutingKey()).isEqualTo(queue.getActualName()); + } + + @Test + public void topicBinding() { + TopicExchange topicExchange = new TopicExchange("t"); + String routingKey = "r"; + Binding binding = BindingBuilder.bind(queue).to(topicExchange).with(routingKey); + assertThat(binding).isNotNull(); + assertThat(binding.getExchange()).isEqualTo(topicExchange.getName()); + assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE); + assertThat(binding.getDestination()).isEqualTo(queue.getActualName()); + assertThat(binding.getRoutingKey()).isEqualTo(routingKey); + } + + @Test + public void headerBinding() { + HeadersExchange headersExchange = new HeadersExchange("h"); + String headerKey = "headerKey"; + Binding binding = BindingBuilder.bind(queue).to(headersExchange).where(headerKey).exists(); + assertThat(binding).isNotNull(); + assertThat(binding.getExchange()).isEqualTo(headersExchange.getName()); + assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE); + assertThat(binding.getDestination()).isEqualTo(queue.getActualName()); + assertThat(binding.getRoutingKey()).isEqualTo(""); + } + + @Test + public void customBinding() { + class CustomExchange extends AbstractExchange { + CustomExchange(String name) { + super(name); + } + + @Override + public String getType() { + return "x-custom"; + } + } + Object argumentObject = new Object(); + CustomExchange customExchange = new CustomExchange("c"); + String routingKey = "r"; + Binding binding = BindingBuilder.// + bind(queue).// + to(customExchange).// + with(routingKey).// + and(Collections.singletonMap("k", argumentObject)); + assertThat(binding).isNotNull(); + assertThat(binding.getArguments().get("k")).isEqualTo(argumentObject); + assertThat(binding.getExchange()).isEqualTo(customExchange.getName()); + assertThat(binding.getDestinationType()).isEqualTo(Binding.DestinationType.QUEUE); + assertThat(binding.getDestination()).isEqualTo(queue.getActualName()); + assertThat(binding.getRoutingKey()).isEqualTo(routingKey); + } + +}