Merge pull request #37576 from vpavic
* gh-37576: Polish "Add support for configuring non-standard JMS acknowledge modes" Add support for configuring non-standard JMS acknowledge modes Closes gh-37576
This commit is contained in:
commit
2d6e61aafa
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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,7 +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.JmsProperties.AcknowledgeMode;
|
||||
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;
|
||||
|
@ -36,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;
|
||||
|
@ -56,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)
|
||||
|
@ -90,9 +97,7 @@ public class JmsAutoConfiguration {
|
|||
|
||||
private void mapTemplateProperties(Template properties, JmsTemplate template) {
|
||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||
map.from(properties.getSession()::getAcknowledgeMode)
|
||||
.asInt(AcknowledgeMode::getMode)
|
||||
.to(template::setSessionAcknowledgeMode);
|
||||
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);
|
||||
|
@ -126,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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -403,47 +403,6 @@ public class JmsProperties {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the acknowledge modes defined on the {@link jakarta.jms.Session}.
|
||||
*
|
||||
* <p>
|
||||
* {@link jakarta.jms.Session#SESSION_TRANSACTED} is not defined as we take care of
|
||||
* this already through a call to {@code setSessionTransacted}.
|
||||
*/
|
||||
public enum AcknowledgeMode {
|
||||
|
||||
/**
|
||||
* Messages sent or received from the session are automatically acknowledged. This
|
||||
* is the simplest mode and enables once-only message delivery guarantee.
|
||||
*/
|
||||
AUTO(1),
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
CLIENT(2),
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
DUPS_OK(3);
|
||||
|
||||
private final int mode;
|
||||
|
||||
AcknowledgeMode(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum DeliveryMode {
|
||||
|
||||
/**
|
||||
|
|
|
@ -3011,6 +3011,40 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.jms.listener.session.acknowledge-mode",
|
||||
"values": [
|
||||
{
|
||||
"value": "auto",
|
||||
"description": "Messages sent or received from the session are automatically acknowledged. This is the simplest mode and enables once-only message delivery guarantee."
|
||||
},
|
||||
{
|
||||
"value": "client",
|
||||
"description": "Messages are acknowledged once the message listener implementation has called \"jakarta.jms.Message#acknowledge()\". This mode gives the application (rather than the JMS provider) complete control over message acknowledgement."
|
||||
},
|
||||
{
|
||||
"value": "dups_ok",
|
||||
"description": "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."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.jms.template.session.acknowledge-mode",
|
||||
"values": [
|
||||
{
|
||||
"value": "auto",
|
||||
"description": "Messages sent or received from the session are automatically acknowledged. This is the simplest mode and enables once-only message delivery guarantee."
|
||||
},
|
||||
{
|
||||
"value": "client",
|
||||
"description": "Messages are acknowledged once the message listener implementation has called \"jakarta.jms.Message#acknowledge()\". This mode gives the application (rather than the JMS provider) complete control over message acknowledgement."
|
||||
},
|
||||
{
|
||||
"value": "dups_ok",
|
||||
"description": "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."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.jmx.server",
|
||||
"providers": [
|
||||
|
|
|
@ -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