mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-07-15 01:07:30 +08:00
Polish "Add support for configuring non-standard JMS acknowledge modes"
See gh-37576
This commit is contained in:
parent
d72fb8e127
commit
6fbc328b4c
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2012-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.boot.autoconfigure.jms;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.jms.Session;
|
||||
|
||||
import org.springframework.jms.support.JmsAccessor;
|
||||
|
||||
/**
|
||||
* Acknowledge modes for a JMS Session. Supports the acknowledge modes defined by
|
||||
* {@link jakarta.jms.Session} as well as other, non-standard modes.
|
||||
*
|
||||
* <p>
|
||||
* Note that {@link jakarta.jms.Session#SESSION_TRANSACTED} is not defined. It should be
|
||||
* handled through a call to {@link JmsAccessor#setSessionTransacted(boolean)}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public final class AcknowledgeMode {
|
||||
|
||||
private static final Map<String, AcknowledgeMode> knownModes = new HashMap<>(3);
|
||||
|
||||
/**
|
||||
* Messages sent or received from the session are automatically acknowledged. This is
|
||||
* the simplest mode and enables once-only message delivery guarantee.
|
||||
*/
|
||||
public static final AcknowledgeMode AUTO = new AcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
|
||||
|
||||
/**
|
||||
* Messages are acknowledged once the message listener implementation has called
|
||||
* {@link jakarta.jms.Message#acknowledge()}. This mode gives the application (rather
|
||||
* than the JMS provider) complete control over message acknowledgement.
|
||||
*/
|
||||
public static final AcknowledgeMode CLIENT = new AcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
|
||||
|
||||
/**
|
||||
* Similar to auto acknowledgment except that said acknowledgment is lazy. As a
|
||||
* consequence, the messages might be delivered more than once. This mode enables
|
||||
* at-least-once message delivery guarantee.
|
||||
*/
|
||||
public static final AcknowledgeMode DUPS_OK = new AcknowledgeMode(Session.DUPS_OK_ACKNOWLEDGE);
|
||||
|
||||
static {
|
||||
knownModes.put("auto", AUTO);
|
||||
knownModes.put("client", CLIENT);
|
||||
knownModes.put("dupsok", DUPS_OK);
|
||||
}
|
||||
|
||||
private final int mode;
|
||||
|
||||
private AcknowledgeMode(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@code AcknowledgeMode} of the given {@code mode}. The mode may be
|
||||
* {@code auto}, {@code client}, {@code dupsok} or a non-standard acknowledge mode
|
||||
* that can be {@link Integer#parseInt parsed as an integer}.
|
||||
* @param mode the mode
|
||||
* @return the acknowledge mode
|
||||
*/
|
||||
public static AcknowledgeMode of(String mode) {
|
||||
String canonicalMode = canonicalize(mode);
|
||||
AcknowledgeMode knownMode = knownModes.get(canonicalMode);
|
||||
try {
|
||||
return (knownMode != null) ? knownMode : new AcknowledgeMode(Integer.parseInt(canonicalMode));
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException("'" + mode
|
||||
+ "' is neither a known acknowledge mode (auto, client, or dups_ok) nor an integer value");
|
||||
}
|
||||
}
|
||||
|
||||
private static String canonicalize(String input) {
|
||||
StringBuilder canonicalName = new StringBuilder(input.length());
|
||||
input.chars()
|
||||
.filter(Character::isLetterOrDigit)
|
||||
.map(Character::toLowerCase)
|
||||
.forEach((c) -> canonicalName.append((char) c));
|
||||
return canonicalName.toString();
|
||||
}
|
||||
|
||||
}
|
@ -111,9 +111,7 @@ public final class DefaultJmsListenerContainerFactoryConfigurer {
|
||||
map.from(this.destinationResolver).to(factory::setDestinationResolver);
|
||||
map.from(this.messageConverter).to(factory::setMessageConverter);
|
||||
map.from(this.exceptionListener).to(factory::setExceptionListener);
|
||||
map.from(sessionProperties.getAcknowledgeMode())
|
||||
.as(JmsAcknowledgeModeMapper::map)
|
||||
.to(factory::setSessionAcknowledgeMode);
|
||||
map.from(sessionProperties.getAcknowledgeMode()::getMode).to(factory::setSessionAcknowledgeMode);
|
||||
if (this.transactionManager == null && sessionProperties.getTransacted() == null) {
|
||||
factory.setSessionTransacted(true);
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-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.boot.autoconfigure.jms;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.jms.Session;
|
||||
|
||||
/**
|
||||
* Helper class used to map JMS acknowledge modes.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
final class JmsAcknowledgeModeMapper {
|
||||
|
||||
private static final Map<String, Integer> acknowledgeModes = new HashMap<>(3);
|
||||
|
||||
static {
|
||||
acknowledgeModes.put("auto", Session.AUTO_ACKNOWLEDGE);
|
||||
acknowledgeModes.put("client", Session.CLIENT_ACKNOWLEDGE);
|
||||
acknowledgeModes.put("dups_ok", Session.DUPS_OK_ACKNOWLEDGE);
|
||||
}
|
||||
|
||||
private JmsAcknowledgeModeMapper() {
|
||||
}
|
||||
|
||||
static int map(String acknowledgeMode) {
|
||||
return acknowledgeModes.computeIfAbsent(acknowledgeMode.toLowerCase(), Integer::parseInt);
|
||||
}
|
||||
|
||||
}
|
@ -17,10 +17,15 @@
|
||||
package org.springframework.boot.autoconfigure.jms;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import jakarta.jms.Message;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@ -28,6 +33,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
|
||||
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration.JmsRuntimeHints;
|
||||
import org.springframework.boot.autoconfigure.jms.JmsProperties.DeliveryMode;
|
||||
import org.springframework.boot.autoconfigure.jms.JmsProperties.Template;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@ -35,6 +41,7 @@ import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.jms.core.JmsMessageOperations;
|
||||
import org.springframework.jms.core.JmsMessagingTemplate;
|
||||
import org.springframework.jms.core.JmsOperations;
|
||||
@ -55,6 +62,7 @@ import org.springframework.jms.support.destination.DestinationResolver;
|
||||
@ConditionalOnBean(ConnectionFactory.class)
|
||||
@EnableConfigurationProperties(JmsProperties.class)
|
||||
@Import(JmsAnnotationDrivenConfiguration.class)
|
||||
@ImportRuntimeHints(JmsRuntimeHints.class)
|
||||
public class JmsAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ -89,9 +97,7 @@ public class JmsAutoConfiguration {
|
||||
|
||||
private void mapTemplateProperties(Template properties, JmsTemplate template) {
|
||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||
map.from(properties.getSession()::getAcknowledgeMode)
|
||||
.to((acknowledgeMode) -> template
|
||||
.setSessionAcknowledgeMode(JmsAcknowledgeModeMapper.map(acknowledgeMode)));
|
||||
map.from(properties.getSession().getAcknowledgeMode()::getMode).to(template::setSessionAcknowledgeMode);
|
||||
map.from(properties.getSession()::isTransacted).to(template::setSessionTransacted);
|
||||
map.from(properties::getDefaultDestination).whenNonNull().to(template::setDefaultDestinationName);
|
||||
map.from(properties::getDeliveryDelay).whenNonNull().as(Duration::toMillis).to(template::setDeliveryDelay);
|
||||
@ -125,4 +131,15 @@ public class JmsAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
static class JmsRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
hints.reflection()
|
||||
.registerType(TypeReference.of(AcknowledgeMode.class), (type) -> type.withMethod("of",
|
||||
List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -171,12 +171,12 @@ public class JmsProperties {
|
||||
|
||||
@Deprecated(since = "3.2.0", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = "spring.jms.listener.session.acknowledge-mode", since = "3.2.0")
|
||||
public String getAcknowledgeMode() {
|
||||
public AcknowledgeMode getAcknowledgeMode() {
|
||||
return this.session.getAcknowledgeMode();
|
||||
}
|
||||
|
||||
@Deprecated(since = "3.2.0", forRemoval = true)
|
||||
public void setAcknowledgeMode(String acknowledgeMode) {
|
||||
public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) {
|
||||
this.session.setAcknowledgeMode(acknowledgeMode);
|
||||
}
|
||||
|
||||
@ -232,7 +232,7 @@ public class JmsProperties {
|
||||
/**
|
||||
* Acknowledge mode of the listener container.
|
||||
*/
|
||||
private String acknowledgeMode = "auto";
|
||||
private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO;
|
||||
|
||||
/**
|
||||
* Whether the listener container should use transacted JMS sessions. Defaults
|
||||
@ -240,11 +240,11 @@ public class JmsProperties {
|
||||
*/
|
||||
private Boolean transacted;
|
||||
|
||||
public String getAcknowledgeMode() {
|
||||
public AcknowledgeMode getAcknowledgeMode() {
|
||||
return this.acknowledgeMode;
|
||||
}
|
||||
|
||||
public void setAcknowledgeMode(String acknowledgeMode) {
|
||||
public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) {
|
||||
this.acknowledgeMode = acknowledgeMode;
|
||||
}
|
||||
|
||||
@ -376,18 +376,18 @@ public class JmsProperties {
|
||||
/**
|
||||
* Acknowledge mode used when creating sessions.
|
||||
*/
|
||||
private String acknowledgeMode = "auto";
|
||||
private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO;
|
||||
|
||||
/**
|
||||
* Whether to use transacted sessions.
|
||||
*/
|
||||
private boolean transacted = false;
|
||||
|
||||
public String getAcknowledgeMode() {
|
||||
public AcknowledgeMode getAcknowledgeMode() {
|
||||
return this.acknowledgeMode;
|
||||
}
|
||||
|
||||
public void setAcknowledgeMode(String acknowledgeMode) {
|
||||
public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) {
|
||||
this.acknowledgeMode = acknowledgeMode;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2012-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.boot.autoconfigure.jms;
|
||||
|
||||
import jakarta.jms.Session;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link AcknowledgeMode}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class AcknowledgeModeTests {
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(Mapping.class)
|
||||
void stringIsMappedToInt(Mapping mapping) {
|
||||
assertThat(AcknowledgeMode.of(mapping.actual)).extracting(AcknowledgeMode::getMode).isEqualTo(mapping.expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapShouldThrowWhenMapIsCalledWithUnknownNonIntegerString() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AcknowledgeMode.of("some-string"))
|
||||
.withMessage(
|
||||
"'some-string' is neither a known acknowledge mode (auto, client, or dups_ok) nor an integer value");
|
||||
}
|
||||
|
||||
private enum Mapping {
|
||||
|
||||
AUTO_LOWER_CASE("auto", Session.AUTO_ACKNOWLEDGE),
|
||||
|
||||
CLIENT_LOWER_CASE("client", Session.CLIENT_ACKNOWLEDGE),
|
||||
|
||||
DUPS_OK_LOWER_CASE("dups_ok", Session.DUPS_OK_ACKNOWLEDGE),
|
||||
|
||||
AUTO_UPPER_CASE("AUTO", Session.AUTO_ACKNOWLEDGE),
|
||||
|
||||
CLIENT_UPPER_CASE("CLIENT", Session.CLIENT_ACKNOWLEDGE),
|
||||
|
||||
DUPS_OK_UPPER_CASE("DUPS_OK", Session.DUPS_OK_ACKNOWLEDGE),
|
||||
|
||||
AUTO_MIXED_CASE("AuTo", Session.AUTO_ACKNOWLEDGE),
|
||||
|
||||
CLIENT_MIXED_CASE("CliEnT", Session.CLIENT_ACKNOWLEDGE),
|
||||
|
||||
DUPS_OK_MIXED_CASE("dUPs_Ok", Session.DUPS_OK_ACKNOWLEDGE),
|
||||
|
||||
DUPS_OK_KEBAB_CASE("DUPS-OK", Session.DUPS_OK_ACKNOWLEDGE),
|
||||
|
||||
DUPS_OK_NO_SEPARATOR_UPPER_CASE("DUPSOK", Session.DUPS_OK_ACKNOWLEDGE),
|
||||
|
||||
DUPS_OK_NO_SEPARATOR_LOWER_CASE("dupsok", Session.DUPS_OK_ACKNOWLEDGE),
|
||||
|
||||
DUPS_OK_NO_SEPARATOR_MIXED_CASE("duPSok", Session.DUPS_OK_ACKNOWLEDGE),
|
||||
|
||||
INTEGER("36", 36);
|
||||
|
||||
private final String actual;
|
||||
|
||||
private final int expected;
|
||||
|
||||
Mapping(String actual, int expected) {
|
||||
this.actual = actual;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -24,14 +24,18 @@ import jakarta.jms.Session;
|
||||
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.aot.ApplicationContextAotGenerator;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.jms.annotation.EnableJms;
|
||||
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
|
||||
@ -160,6 +164,16 @@ class JmsAutoConfigurationTests {
|
||||
assertThat(container).hasFieldOrPropertyWithValue("receiveTimeout", 2000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsListenerContainerFactoryWithNonStandardAcknowledgeMode() {
|
||||
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class)
|
||||
.withPropertyValues("spring.jms.listener.session.acknowledge-mode=9")
|
||||
.run((context) -> {
|
||||
DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory");
|
||||
assertThat(container.getSessionAcknowledgeMode()).isEqualTo(9);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsListenerContainerFactoryWithDefaultSettings() {
|
||||
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class)
|
||||
@ -300,6 +314,16 @@ class JmsAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsTemplateWithNonStandardAcknowledgeMode() {
|
||||
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class)
|
||||
.withPropertyValues("spring.jms.template.session.acknowledge-mode=7")
|
||||
.run((context) -> {
|
||||
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
|
||||
assertThat(jmsTemplate.getSessionAcknowledgeMode()).isEqualTo(7);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsMessagingTemplateUseConfiguredDefaultDestination() {
|
||||
this.contextRunner.withPropertyValues("spring.jms.template.default-destination=testQueue").run((context) -> {
|
||||
@ -367,6 +391,17 @@ class JmsAutoConfigurationTests {
|
||||
.hasBean(JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
void runtimeHintsAreRegisteredForBindingOfAcknowledgeMode() {
|
||||
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
|
||||
context.register(ArtemisAutoConfiguration.class, JmsAutoConfiguration.class);
|
||||
TestGenerationContext generationContext = new TestGenerationContext();
|
||||
new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(AcknowledgeMode.class, "of").invoke())
|
||||
.accepts(generationContext.getRuntimeHints());
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user