Create spring-boot-jms module
This commit is contained in:
committed by
Phillip Webb
parent
b0f5788d9a
commit
f78a6fa37a
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms;
|
||||
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import org.messaginghub.pooled.jms.JmsPoolConnectionFactory;
|
||||
|
||||
import org.springframework.jms.connection.CachingConnectionFactory;
|
||||
|
||||
/**
|
||||
* Unwrap a {@link ConnectionFactory} that may have been wrapped to perform caching or
|
||||
* pooling.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public final class ConnectionFactoryUnwrapper {
|
||||
|
||||
private ConnectionFactoryUnwrapper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the native {@link ConnectionFactory} by unwrapping from a
|
||||
* {@link CachingConnectionFactory}. Return the given {@link ConnectionFactory} if no
|
||||
* {@link CachingConnectionFactory} wrapper has been detected.
|
||||
* @param connectionFactory a connection factory
|
||||
* @return the native connection factory that a {@link CachingConnectionFactory}
|
||||
* wraps, if any
|
||||
* @since 3.4.1
|
||||
*/
|
||||
public static ConnectionFactory unwrapCaching(ConnectionFactory connectionFactory) {
|
||||
if (connectionFactory instanceof CachingConnectionFactory cachingConnectionFactory) {
|
||||
ConnectionFactory unwrapedConnectionFactory = cachingConnectionFactory.getTargetConnectionFactory();
|
||||
return (unwrapedConnectionFactory != null) ? unwrapCaching(unwrapedConnectionFactory) : connectionFactory;
|
||||
}
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the native {@link ConnectionFactory} by unwrapping it from a cache or pool
|
||||
* connection factory. Return the given {@link ConnectionFactory} if no caching
|
||||
* wrapper has been detected.
|
||||
* @param connectionFactory a connection factory
|
||||
* @return the native connection factory that it wraps, if any
|
||||
*/
|
||||
public static ConnectionFactory unwrap(ConnectionFactory connectionFactory) {
|
||||
if (connectionFactory instanceof CachingConnectionFactory cachingConnectionFactory) {
|
||||
return unwrap(cachingConnectionFactory.getTargetConnectionFactory());
|
||||
}
|
||||
ConnectionFactory unwrapedConnectionFactory = unwrapFromJmsPoolConnectionFactory(connectionFactory);
|
||||
return (unwrapedConnectionFactory != null) ? unwrap(unwrapedConnectionFactory) : connectionFactory;
|
||||
}
|
||||
|
||||
private static ConnectionFactory unwrapFromJmsPoolConnectionFactory(ConnectionFactory connectionFactory) {
|
||||
try {
|
||||
if (connectionFactory instanceof JmsPoolConnectionFactory jmsPoolConnectionFactory) {
|
||||
return (ConnectionFactory) jmsPoolConnectionFactory.getConnectionFactory();
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms;
|
||||
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import jakarta.jms.XAConnectionFactory;
|
||||
import jakarta.transaction.TransactionManager;
|
||||
|
||||
/**
|
||||
* Strategy interface used to wrap a JMS {@link XAConnectionFactory} enrolling it with a
|
||||
* JTA {@link TransactionManager}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface XAConnectionFactoryWrapper {
|
||||
|
||||
/**
|
||||
* Wrap the specific {@link XAConnectionFactory} and enroll it with a JTA
|
||||
* {@link TransactionManager}.
|
||||
* @param connectionFactory the connection factory to wrap
|
||||
* @return the wrapped connection factory
|
||||
* @throws Exception if the connection factory cannot be wrapped
|
||||
*/
|
||||
ConnectionFactory wrapConnectionFactory(XAConnectionFactory connectionFactory) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import jakarta.jms.ExceptionListener;
|
||||
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.jms.autoconfigure.JmsProperties.Listener.Session;
|
||||
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
|
||||
import org.springframework.jms.support.converter.MessageConverter;
|
||||
import org.springframework.jms.support.destination.DestinationResolver;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Configure {@link DefaultJmsListenerContainerFactory} with sensible defaults tuned using
|
||||
* configuration properties.
|
||||
* <p>
|
||||
* Can be injected into application code and used to define a custom
|
||||
* {@code DefaultJmsListenerContainerFactory} whose configuration is based upon that
|
||||
* produced by auto-configuration.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Eddú Meléndez
|
||||
* @author Vedran Pavic
|
||||
* @author Lasse Wulff
|
||||
* @since 1.3.3
|
||||
*/
|
||||
public final class DefaultJmsListenerContainerFactoryConfigurer {
|
||||
|
||||
private DestinationResolver destinationResolver;
|
||||
|
||||
private MessageConverter messageConverter;
|
||||
|
||||
private ExceptionListener exceptionListener;
|
||||
|
||||
private JtaTransactionManager transactionManager;
|
||||
|
||||
private JmsProperties jmsProperties;
|
||||
|
||||
private ObservationRegistry observationRegistry;
|
||||
|
||||
/**
|
||||
* Set the {@link DestinationResolver} to use or {@code null} if no destination
|
||||
* resolver should be associated with the factory by default.
|
||||
* @param destinationResolver the {@link DestinationResolver}
|
||||
*/
|
||||
void setDestinationResolver(DestinationResolver destinationResolver) {
|
||||
this.destinationResolver = destinationResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link MessageConverter} to use or {@code null} if the out-of-the-box
|
||||
* converter should be used.
|
||||
* @param messageConverter the {@link MessageConverter}
|
||||
*/
|
||||
void setMessageConverter(MessageConverter messageConverter) {
|
||||
this.messageConverter = messageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ExceptionListener} to use or {@code null} if no exception listener
|
||||
* should be associated by default.
|
||||
* @param exceptionListener the {@link ExceptionListener}
|
||||
*/
|
||||
void setExceptionListener(ExceptionListener exceptionListener) {
|
||||
this.exceptionListener = exceptionListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link JtaTransactionManager} to use or {@code null} if the JTA support
|
||||
* should not be used.
|
||||
* @param transactionManager the {@link JtaTransactionManager}
|
||||
*/
|
||||
void setTransactionManager(JtaTransactionManager transactionManager) {
|
||||
this.transactionManager = transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link JmsProperties} to use.
|
||||
* @param jmsProperties the {@link JmsProperties}
|
||||
*/
|
||||
void setJmsProperties(JmsProperties jmsProperties) {
|
||||
this.jmsProperties = jmsProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ObservationRegistry} to use.
|
||||
* @param observationRegistry the {@link ObservationRegistry}
|
||||
* @since 3.2.1
|
||||
* @deprecated since 3.3.10 for removal in 4.0.0 as this should have been package
|
||||
* private
|
||||
*/
|
||||
@Deprecated(since = "3.3.10", forRemoval = true)
|
||||
public void setObservationRegistry(ObservationRegistry observationRegistry) {
|
||||
this.observationRegistry = observationRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the specified jms listener container factory. The factory can be further
|
||||
* tuned and default settings can be overridden.
|
||||
* @param factory the {@link DefaultJmsListenerContainerFactory} instance to configure
|
||||
* @param connectionFactory the {@link ConnectionFactory} to use
|
||||
*/
|
||||
public void configure(DefaultJmsListenerContainerFactory factory, ConnectionFactory connectionFactory) {
|
||||
Assert.notNull(factory, "'factory' must not be null");
|
||||
Assert.notNull(connectionFactory, "'connectionFactory' must not be null");
|
||||
JmsProperties.Listener listenerProperties = this.jmsProperties.getListener();
|
||||
Session sessionProperties = listenerProperties.getSession();
|
||||
factory.setConnectionFactory(connectionFactory);
|
||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||
map.from(this.jmsProperties::isPubSubDomain).to(factory::setPubSubDomain);
|
||||
map.from(this.jmsProperties::isSubscriptionDurable).to(factory::setSubscriptionDurable);
|
||||
map.from(this.jmsProperties::getClientId).to(factory::setClientId);
|
||||
map.from(this.transactionManager).to(factory::setTransactionManager);
|
||||
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()::getMode).to(factory::setSessionAcknowledgeMode);
|
||||
if (this.transactionManager == null && sessionProperties.getTransacted() == null) {
|
||||
factory.setSessionTransacted(true);
|
||||
}
|
||||
map.from(this.observationRegistry).to(factory::setObservationRegistry);
|
||||
map.from(sessionProperties::getTransacted).to(factory::setSessionTransacted);
|
||||
map.from(listenerProperties::isAutoStartup).to(factory::setAutoStartup);
|
||||
map.from(listenerProperties::formatConcurrency).to(factory::setConcurrency);
|
||||
map.from(listenerProperties::getReceiveTimeout).as(Duration::toMillis).to(factory::setReceiveTimeout);
|
||||
map.from(listenerProperties::getMaxMessagesPerTask).to(factory::setMaxMessagesPerTask);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import jakarta.jms.ExceptionListener;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
|
||||
import org.springframework.boot.jms.ConnectionFactoryUnwrapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jms.annotation.EnableJms;
|
||||
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
|
||||
import org.springframework.jms.config.JmsListenerConfigUtils;
|
||||
import org.springframework.jms.support.converter.MessageConverter;
|
||||
import org.springframework.jms.support.destination.DestinationResolver;
|
||||
import org.springframework.jms.support.destination.JndiDestinationResolver;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
|
||||
/**
|
||||
* Configuration for Spring 4.1 annotation driven JMS.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @author Eddú Meléndez
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(EnableJms.class)
|
||||
class JmsAnnotationDrivenConfiguration {
|
||||
|
||||
private final ObjectProvider<DestinationResolver> destinationResolver;
|
||||
|
||||
private final ObjectProvider<JtaTransactionManager> transactionManager;
|
||||
|
||||
private final ObjectProvider<MessageConverter> messageConverter;
|
||||
|
||||
private final ObjectProvider<ExceptionListener> exceptionListener;
|
||||
|
||||
private final ObjectProvider<ObservationRegistry> observationRegistry;
|
||||
|
||||
private final JmsProperties properties;
|
||||
|
||||
JmsAnnotationDrivenConfiguration(ObjectProvider<DestinationResolver> destinationResolver,
|
||||
ObjectProvider<JtaTransactionManager> transactionManager, ObjectProvider<MessageConverter> messageConverter,
|
||||
ObjectProvider<ExceptionListener> exceptionListener,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry, JmsProperties properties) {
|
||||
this.destinationResolver = destinationResolver;
|
||||
this.transactionManager = transactionManager;
|
||||
this.messageConverter = messageConverter;
|
||||
this.exceptionListener = exceptionListener;
|
||||
this.observationRegistry = observationRegistry;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@SuppressWarnings("removal")
|
||||
DefaultJmsListenerContainerFactoryConfigurer jmsListenerContainerFactoryConfigurer() {
|
||||
DefaultJmsListenerContainerFactoryConfigurer configurer = new DefaultJmsListenerContainerFactoryConfigurer();
|
||||
configurer.setDestinationResolver(this.destinationResolver.getIfUnique());
|
||||
configurer.setTransactionManager(this.transactionManager.getIfUnique());
|
||||
configurer.setMessageConverter(this.messageConverter.getIfUnique());
|
||||
configurer.setExceptionListener(this.exceptionListener.getIfUnique());
|
||||
configurer.setObservationRegistry(this.observationRegistry.getIfUnique());
|
||||
configurer.setJmsProperties(this.properties);
|
||||
return configurer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnSingleCandidate(ConnectionFactory.class)
|
||||
@ConditionalOnMissingBean(name = "jmsListenerContainerFactory")
|
||||
DefaultJmsListenerContainerFactory jmsListenerContainerFactory(
|
||||
DefaultJmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
|
||||
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
|
||||
configurer.configure(factory, ConnectionFactoryUnwrapper.unwrapCaching(connectionFactory));
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableJms
|
||||
@ConditionalOnMissingBean(name = JmsListenerConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
|
||||
static class EnableJmsConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnJndi
|
||||
static class JndiConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(DestinationResolver.class)
|
||||
JndiDestinationResolver destinationResolver() {
|
||||
JndiDestinationResolver resolver = new JndiDestinationResolver();
|
||||
resolver.setFallbackToDynamicDestination(true);
|
||||
return resolver;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
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;
|
||||
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.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.jms.autoconfigure.JmsAutoConfiguration.JmsRuntimeHints;
|
||||
import org.springframework.boot.jms.autoconfigure.JmsProperties.DeliveryMode;
|
||||
import org.springframework.boot.jms.autoconfigure.JmsProperties.Template;
|
||||
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;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
import org.springframework.jms.support.converter.MessageConverter;
|
||||
import org.springframework.jms.support.destination.DestinationResolver;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for Spring JMS.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Stephane Nicoll
|
||||
* @author Vedran Pavic
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({ Message.class, JmsTemplate.class })
|
||||
@ConditionalOnBean(ConnectionFactory.class)
|
||||
@EnableConfigurationProperties(JmsProperties.class)
|
||||
@Import(JmsAnnotationDrivenConfiguration.class)
|
||||
@ImportRuntimeHints(JmsRuntimeHints.class)
|
||||
public class JmsAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
protected static class JmsTemplateConfiguration {
|
||||
|
||||
private final JmsProperties properties;
|
||||
|
||||
private final ObjectProvider<DestinationResolver> destinationResolver;
|
||||
|
||||
private final ObjectProvider<MessageConverter> messageConverter;
|
||||
|
||||
private final ObjectProvider<ObservationRegistry> observationRegistry;
|
||||
|
||||
public JmsTemplateConfiguration(JmsProperties properties,
|
||||
ObjectProvider<DestinationResolver> destinationResolver,
|
||||
ObjectProvider<MessageConverter> messageConverter,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry) {
|
||||
this.properties = properties;
|
||||
this.destinationResolver = destinationResolver;
|
||||
this.messageConverter = messageConverter;
|
||||
this.observationRegistry = observationRegistry;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(JmsOperations.class)
|
||||
@ConditionalOnSingleCandidate(ConnectionFactory.class)
|
||||
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
JmsTemplate template = new JmsTemplate(connectionFactory);
|
||||
template.setPubSubDomain(this.properties.isPubSubDomain());
|
||||
map.from(this.destinationResolver::getIfUnique).whenNonNull().to(template::setDestinationResolver);
|
||||
map.from(this.messageConverter::getIfUnique).whenNonNull().to(template::setMessageConverter);
|
||||
map.from(this.observationRegistry::getIfUnique).whenNonNull().to(template::setObservationRegistry);
|
||||
mapTemplateProperties(this.properties.getTemplate(), template);
|
||||
return template;
|
||||
}
|
||||
|
||||
private void mapTemplateProperties(Template properties, JmsTemplate template) {
|
||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||
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);
|
||||
map.from(properties::determineQosEnabled).to(template::setExplicitQosEnabled);
|
||||
map.from(properties::getDeliveryMode).as(DeliveryMode::getValue).to(template::setDeliveryMode);
|
||||
map.from(properties::getPriority).whenNonNull().to(template::setPriority);
|
||||
map.from(properties::getTimeToLive).whenNonNull().as(Duration::toMillis).to(template::setTimeToLive);
|
||||
map.from(properties::getReceiveTimeout).as(Duration::toMillis).to(template::setReceiveTimeout);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(JmsMessagingTemplate.class)
|
||||
@Import(JmsTemplateConfiguration.class)
|
||||
protected static class MessagingTemplateConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(JmsMessageOperations.class)
|
||||
@ConditionalOnSingleCandidate(JmsTemplate.class)
|
||||
public JmsMessagingTemplate jmsMessagingTemplate(JmsProperties properties, JmsTemplate jmsTemplate) {
|
||||
JmsMessagingTemplate messagingTemplate = new JmsMessagingTemplate(jmsTemplate);
|
||||
mapTemplateProperties(properties.getTemplate(), messagingTemplate);
|
||||
return messagingTemplate;
|
||||
}
|
||||
|
||||
private void mapTemplateProperties(Template properties, JmsMessagingTemplate messagingTemplate) {
|
||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||
map.from(properties::getDefaultDestination).to(messagingTemplate::setDefaultDestinationName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import org.messaginghub.pooled.jms.JmsPoolConnectionFactory;
|
||||
|
||||
/**
|
||||
* Factory to create a {@link JmsPoolConnectionFactory} from properties defined in
|
||||
* {@link JmsPoolConnectionFactoryProperties}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public class JmsPoolConnectionFactoryFactory {
|
||||
|
||||
private final JmsPoolConnectionFactoryProperties properties;
|
||||
|
||||
public JmsPoolConnectionFactoryFactory(JmsPoolConnectionFactoryProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link JmsPoolConnectionFactory} based on the specified
|
||||
* {@link ConnectionFactory}.
|
||||
* @param connectionFactory the connection factory to wrap
|
||||
* @return a pooled connection factory
|
||||
*/
|
||||
public JmsPoolConnectionFactory createPooledConnectionFactory(ConnectionFactory connectionFactory) {
|
||||
JmsPoolConnectionFactory pooledConnectionFactory = new JmsPoolConnectionFactory();
|
||||
pooledConnectionFactory.setConnectionFactory(connectionFactory);
|
||||
|
||||
pooledConnectionFactory.setBlockIfSessionPoolIsFull(this.properties.isBlockIfFull());
|
||||
if (this.properties.getBlockIfFullTimeout() != null) {
|
||||
pooledConnectionFactory
|
||||
.setBlockIfSessionPoolIsFullTimeout(this.properties.getBlockIfFullTimeout().toMillis());
|
||||
}
|
||||
if (this.properties.getIdleTimeout() != null) {
|
||||
pooledConnectionFactory.setConnectionIdleTimeout((int) this.properties.getIdleTimeout().toMillis());
|
||||
}
|
||||
pooledConnectionFactory.setMaxConnections(this.properties.getMaxConnections());
|
||||
pooledConnectionFactory.setMaxSessionsPerConnection(this.properties.getMaxSessionsPerConnection());
|
||||
if (this.properties.getTimeBetweenExpirationCheck() != null) {
|
||||
pooledConnectionFactory
|
||||
.setConnectionCheckInterval(this.properties.getTimeBetweenExpirationCheck().toMillis());
|
||||
}
|
||||
pooledConnectionFactory.setUseAnonymousProducers(this.properties.isUseAnonymousProducers());
|
||||
return pooledConnectionFactory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Configuration properties for connection factory pooling.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public class JmsPoolConnectionFactoryProperties {
|
||||
|
||||
/**
|
||||
* Whether a JmsPoolConnectionFactory should be created, instead of a regular
|
||||
* ConnectionFactory.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* Whether to block when a connection is requested and the pool is full. Set it to
|
||||
* false to throw a "JMSException" instead.
|
||||
*/
|
||||
private boolean blockIfFull = true;
|
||||
|
||||
/**
|
||||
* Blocking period before throwing an exception if the pool is still full.
|
||||
*/
|
||||
private Duration blockIfFullTimeout = Duration.ofMillis(-1);
|
||||
|
||||
/**
|
||||
* Connection idle timeout.
|
||||
*/
|
||||
private Duration idleTimeout = Duration.ofSeconds(30);
|
||||
|
||||
/**
|
||||
* Maximum number of pooled connections.
|
||||
*/
|
||||
private int maxConnections = 1;
|
||||
|
||||
/**
|
||||
* Maximum number of pooled sessions per connection in the pool.
|
||||
*/
|
||||
private int maxSessionsPerConnection = 500;
|
||||
|
||||
/**
|
||||
* Time to sleep between runs of the idle connection eviction thread. When negative,
|
||||
* no idle connection eviction thread runs.
|
||||
*/
|
||||
private Duration timeBetweenExpirationCheck = Duration.ofMillis(-1);
|
||||
|
||||
/**
|
||||
* Whether to use only one anonymous "MessageProducer" instance. Set it to false to
|
||||
* create one "MessageProducer" every time one is required.
|
||||
*/
|
||||
private boolean useAnonymousProducers = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isBlockIfFull() {
|
||||
return this.blockIfFull;
|
||||
}
|
||||
|
||||
public void setBlockIfFull(boolean blockIfFull) {
|
||||
this.blockIfFull = blockIfFull;
|
||||
}
|
||||
|
||||
public Duration getBlockIfFullTimeout() {
|
||||
return this.blockIfFullTimeout;
|
||||
}
|
||||
|
||||
public void setBlockIfFullTimeout(Duration blockIfFullTimeout) {
|
||||
this.blockIfFullTimeout = blockIfFullTimeout;
|
||||
}
|
||||
|
||||
public Duration getIdleTimeout() {
|
||||
return this.idleTimeout;
|
||||
}
|
||||
|
||||
public void setIdleTimeout(Duration idleTimeout) {
|
||||
this.idleTimeout = idleTimeout;
|
||||
}
|
||||
|
||||
public int getMaxConnections() {
|
||||
return this.maxConnections;
|
||||
}
|
||||
|
||||
public void setMaxConnections(int maxConnections) {
|
||||
this.maxConnections = maxConnections;
|
||||
}
|
||||
|
||||
public int getMaxSessionsPerConnection() {
|
||||
return this.maxSessionsPerConnection;
|
||||
}
|
||||
|
||||
public void setMaxSessionsPerConnection(int maxSessionsPerConnection) {
|
||||
this.maxSessionsPerConnection = maxSessionsPerConnection;
|
||||
}
|
||||
|
||||
public Duration getTimeBetweenExpirationCheck() {
|
||||
return this.timeBetweenExpirationCheck;
|
||||
}
|
||||
|
||||
public void setTimeBetweenExpirationCheck(Duration timeBetweenExpirationCheck) {
|
||||
this.timeBetweenExpirationCheck = timeBetweenExpirationCheck;
|
||||
}
|
||||
|
||||
public boolean isUseAnonymousProducers() {
|
||||
return this.useAnonymousProducers;
|
||||
}
|
||||
|
||||
public void setUseAnonymousProducers(boolean useAnonymousProducers) {
|
||||
this.useAnonymousProducers = useAnonymousProducers;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
|
||||
|
||||
/**
|
||||
* Configuration properties for JMS.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @author Lasse Wulff
|
||||
* @author Vedran Pavic
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.jms")
|
||||
public class JmsProperties {
|
||||
|
||||
/**
|
||||
* Whether the default destination type is topic.
|
||||
*/
|
||||
private boolean pubSubDomain = false;
|
||||
|
||||
/**
|
||||
* Connection factory JNDI name. When set, takes precedence to others connection
|
||||
* factory auto-configurations.
|
||||
*/
|
||||
private String jndiName;
|
||||
|
||||
/**
|
||||
* Whether the subscription is durable.
|
||||
*/
|
||||
private boolean subscriptionDurable = false;
|
||||
|
||||
/**
|
||||
* Client id of the connection.
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
private final Cache cache = new Cache();
|
||||
|
||||
private final Listener listener = new Listener();
|
||||
|
||||
private final Template template = new Template();
|
||||
|
||||
public boolean isPubSubDomain() {
|
||||
return this.pubSubDomain;
|
||||
}
|
||||
|
||||
public void setPubSubDomain(boolean pubSubDomain) {
|
||||
this.pubSubDomain = pubSubDomain;
|
||||
}
|
||||
|
||||
public boolean isSubscriptionDurable() {
|
||||
return this.subscriptionDurable;
|
||||
}
|
||||
|
||||
public void setSubscriptionDurable(boolean subscriptionDurable) {
|
||||
this.subscriptionDurable = subscriptionDurable;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getJndiName() {
|
||||
return this.jndiName;
|
||||
}
|
||||
|
||||
public void setJndiName(String jndiName) {
|
||||
this.jndiName = jndiName;
|
||||
}
|
||||
|
||||
public Cache getCache() {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
public Listener getListener() {
|
||||
return this.listener;
|
||||
}
|
||||
|
||||
public Template getTemplate() {
|
||||
return this.template;
|
||||
}
|
||||
|
||||
public static class Cache {
|
||||
|
||||
/**
|
||||
* Whether to cache sessions.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Whether to cache message consumers.
|
||||
*/
|
||||
private boolean consumers = false;
|
||||
|
||||
/**
|
||||
* Whether to cache message producers.
|
||||
*/
|
||||
private boolean producers = true;
|
||||
|
||||
/**
|
||||
* Size of the session cache (per JMS Session type).
|
||||
*/
|
||||
private int sessionCacheSize = 1;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isConsumers() {
|
||||
return this.consumers;
|
||||
}
|
||||
|
||||
public void setConsumers(boolean consumers) {
|
||||
this.consumers = consumers;
|
||||
}
|
||||
|
||||
public boolean isProducers() {
|
||||
return this.producers;
|
||||
}
|
||||
|
||||
public void setProducers(boolean producers) {
|
||||
this.producers = producers;
|
||||
}
|
||||
|
||||
public int getSessionCacheSize() {
|
||||
return this.sessionCacheSize;
|
||||
}
|
||||
|
||||
public void setSessionCacheSize(int sessionCacheSize) {
|
||||
this.sessionCacheSize = sessionCacheSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Listener {
|
||||
|
||||
/**
|
||||
* Start the container automatically on startup.
|
||||
*/
|
||||
private boolean autoStartup = true;
|
||||
|
||||
/**
|
||||
* Minimum number of concurrent consumers. When max-concurrency is not specified
|
||||
* the minimum will also be used as the maximum.
|
||||
*/
|
||||
private Integer minConcurrency;
|
||||
|
||||
/**
|
||||
* Maximum number of concurrent consumers.
|
||||
*/
|
||||
private Integer maxConcurrency;
|
||||
|
||||
/**
|
||||
* Timeout to use for receive calls. Use -1 for a no-wait receive or 0 for no
|
||||
* timeout at all. The latter is only feasible if not running within a transaction
|
||||
* manager and is generally discouraged since it prevents clean shutdown.
|
||||
*/
|
||||
private Duration receiveTimeout = Duration.ofSeconds(1);
|
||||
|
||||
/**
|
||||
* Maximum number of messages to process in one task. By default, unlimited unless
|
||||
* a SchedulingTaskExecutor is configured on the listener (10 messages), as it
|
||||
* indicates a preference for short-lived tasks.
|
||||
*/
|
||||
private Integer maxMessagesPerTask;
|
||||
|
||||
private final Session session = new Session();
|
||||
|
||||
public boolean isAutoStartup() {
|
||||
return this.autoStartup;
|
||||
}
|
||||
|
||||
public void setAutoStartup(boolean autoStartup) {
|
||||
this.autoStartup = autoStartup;
|
||||
}
|
||||
|
||||
@Deprecated(since = "3.2.0", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = "spring.jms.listener.session.acknowledge-mode", since = "3.2.0")
|
||||
public AcknowledgeMode getAcknowledgeMode() {
|
||||
return this.session.getAcknowledgeMode();
|
||||
}
|
||||
|
||||
@Deprecated(since = "3.2.0", forRemoval = true)
|
||||
public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) {
|
||||
this.session.setAcknowledgeMode(acknowledgeMode);
|
||||
}
|
||||
|
||||
@DeprecatedConfigurationProperty(replacement = "spring.jms.listener.min-concurrency", since = "3.2.0")
|
||||
@Deprecated(since = "3.2.0", forRemoval = true)
|
||||
public Integer getConcurrency() {
|
||||
return this.minConcurrency;
|
||||
}
|
||||
|
||||
@Deprecated(since = "3.2.0", forRemoval = true)
|
||||
public void setConcurrency(Integer concurrency) {
|
||||
this.minConcurrency = concurrency;
|
||||
}
|
||||
|
||||
public Integer getMinConcurrency() {
|
||||
return this.minConcurrency;
|
||||
}
|
||||
|
||||
public void setMinConcurrency(Integer minConcurrency) {
|
||||
this.minConcurrency = minConcurrency;
|
||||
}
|
||||
|
||||
public Integer getMaxConcurrency() {
|
||||
return this.maxConcurrency;
|
||||
}
|
||||
|
||||
public void setMaxConcurrency(Integer maxConcurrency) {
|
||||
this.maxConcurrency = maxConcurrency;
|
||||
}
|
||||
|
||||
public String formatConcurrency() {
|
||||
if (this.minConcurrency == null) {
|
||||
return (this.maxConcurrency != null) ? "1-" + this.maxConcurrency : null;
|
||||
}
|
||||
return this.minConcurrency + "-"
|
||||
+ ((this.maxConcurrency != null) ? this.maxConcurrency : this.minConcurrency);
|
||||
}
|
||||
|
||||
public Duration getReceiveTimeout() {
|
||||
return this.receiveTimeout;
|
||||
}
|
||||
|
||||
public void setReceiveTimeout(Duration receiveTimeout) {
|
||||
this.receiveTimeout = receiveTimeout;
|
||||
}
|
||||
|
||||
public Integer getMaxMessagesPerTask() {
|
||||
return this.maxMessagesPerTask;
|
||||
}
|
||||
|
||||
public void setMaxMessagesPerTask(Integer maxMessagesPerTask) {
|
||||
this.maxMessagesPerTask = maxMessagesPerTask;
|
||||
}
|
||||
|
||||
public Session getSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
public static class Session {
|
||||
|
||||
/**
|
||||
* Acknowledge mode of the listener container.
|
||||
*/
|
||||
private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO;
|
||||
|
||||
/**
|
||||
* Whether the listener container should use transacted JMS sessions. Defaults
|
||||
* to false in the presence of a JtaTransactionManager and true otherwise.
|
||||
*/
|
||||
private Boolean transacted;
|
||||
|
||||
public AcknowledgeMode getAcknowledgeMode() {
|
||||
return this.acknowledgeMode;
|
||||
}
|
||||
|
||||
public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) {
|
||||
this.acknowledgeMode = acknowledgeMode;
|
||||
}
|
||||
|
||||
public Boolean getTransacted() {
|
||||
return this.transacted;
|
||||
}
|
||||
|
||||
public void setTransacted(Boolean transacted) {
|
||||
this.transacted = transacted;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Template {
|
||||
|
||||
/**
|
||||
* Default destination to use on send and receive operations that do not have a
|
||||
* destination parameter.
|
||||
*/
|
||||
private String defaultDestination;
|
||||
|
||||
/**
|
||||
* Delivery delay to use for send calls.
|
||||
*/
|
||||
private Duration deliveryDelay;
|
||||
|
||||
/**
|
||||
* Delivery mode. Enables QoS (Quality of Service) when set.
|
||||
*/
|
||||
private DeliveryMode deliveryMode;
|
||||
|
||||
/**
|
||||
* Priority of a message when sending. Enables QoS (Quality of Service) when set.
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* Time-to-live of a message when sending. Enables QoS (Quality of Service) when
|
||||
* set.
|
||||
*/
|
||||
private Duration timeToLive;
|
||||
|
||||
/**
|
||||
* Whether to enable explicit QoS (Quality of Service) when sending a message.
|
||||
* When enabled, the delivery mode, priority and time-to-live properties will be
|
||||
* used when sending a message. QoS is automatically enabled when at least one of
|
||||
* those settings is customized.
|
||||
*/
|
||||
private Boolean qosEnabled;
|
||||
|
||||
/**
|
||||
* Timeout to use for receive calls.
|
||||
*/
|
||||
private Duration receiveTimeout;
|
||||
|
||||
private final Session session = new Session();
|
||||
|
||||
public String getDefaultDestination() {
|
||||
return this.defaultDestination;
|
||||
}
|
||||
|
||||
public void setDefaultDestination(String defaultDestination) {
|
||||
this.defaultDestination = defaultDestination;
|
||||
}
|
||||
|
||||
public Duration getDeliveryDelay() {
|
||||
return this.deliveryDelay;
|
||||
}
|
||||
|
||||
public void setDeliveryDelay(Duration deliveryDelay) {
|
||||
this.deliveryDelay = deliveryDelay;
|
||||
}
|
||||
|
||||
public DeliveryMode getDeliveryMode() {
|
||||
return this.deliveryMode;
|
||||
}
|
||||
|
||||
public void setDeliveryMode(DeliveryMode deliveryMode) {
|
||||
this.deliveryMode = deliveryMode;
|
||||
}
|
||||
|
||||
public Integer getPriority() {
|
||||
return this.priority;
|
||||
}
|
||||
|
||||
public void setPriority(Integer priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public Duration getTimeToLive() {
|
||||
return this.timeToLive;
|
||||
}
|
||||
|
||||
public void setTimeToLive(Duration timeToLive) {
|
||||
this.timeToLive = timeToLive;
|
||||
}
|
||||
|
||||
public boolean determineQosEnabled() {
|
||||
if (this.qosEnabled != null) {
|
||||
return this.qosEnabled;
|
||||
}
|
||||
return (getDeliveryMode() != null || getPriority() != null || getTimeToLive() != null);
|
||||
}
|
||||
|
||||
public Boolean getQosEnabled() {
|
||||
return this.qosEnabled;
|
||||
}
|
||||
|
||||
public void setQosEnabled(Boolean qosEnabled) {
|
||||
this.qosEnabled = qosEnabled;
|
||||
}
|
||||
|
||||
public Duration getReceiveTimeout() {
|
||||
return this.receiveTimeout;
|
||||
}
|
||||
|
||||
public void setReceiveTimeout(Duration receiveTimeout) {
|
||||
this.receiveTimeout = receiveTimeout;
|
||||
}
|
||||
|
||||
public Session getSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
public static class Session {
|
||||
|
||||
/**
|
||||
* Acknowledge mode used when creating sessions.
|
||||
*/
|
||||
private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO;
|
||||
|
||||
/**
|
||||
* Whether to use transacted sessions.
|
||||
*/
|
||||
private boolean transacted = false;
|
||||
|
||||
public AcknowledgeMode getAcknowledgeMode() {
|
||||
return this.acknowledgeMode;
|
||||
}
|
||||
|
||||
public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) {
|
||||
this.acknowledgeMode = acknowledgeMode;
|
||||
}
|
||||
|
||||
public boolean isTransacted() {
|
||||
return this.transacted;
|
||||
}
|
||||
|
||||
public void setTransacted(boolean transacted) {
|
||||
this.transacted = transacted;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum DeliveryMode {
|
||||
|
||||
/**
|
||||
* Does not require that the message be logged to stable storage. This is the
|
||||
* lowest-overhead delivery mode but can lead to lost of message if the broker
|
||||
* goes down.
|
||||
*/
|
||||
NON_PERSISTENT(1),
|
||||
|
||||
/*
|
||||
* Instructs the JMS provider to log the message to stable storage as part of the
|
||||
* client's send operation.
|
||||
*/
|
||||
PERSISTENT(2);
|
||||
|
||||
private final int value;
|
||||
|
||||
DeliveryMode(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.jms.autoconfigure.JndiConnectionFactoryAutoConfiguration.JndiOrPropertyCondition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
import org.springframework.jndi.JndiLocatorDelegate;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for JMS provided from JNDI.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@AutoConfiguration(before = JmsAutoConfiguration.class)
|
||||
@ConditionalOnClass(JmsTemplate.class)
|
||||
@ConditionalOnMissingBean(ConnectionFactory.class)
|
||||
@Conditional(JndiOrPropertyCondition.class)
|
||||
@EnableConfigurationProperties(JmsProperties.class)
|
||||
public class JndiConnectionFactoryAutoConfiguration {
|
||||
|
||||
// Keep these in sync with the condition below
|
||||
private static final String[] JNDI_LOCATIONS = { "java:/JmsXA", "java:/XAConnectionFactory" };
|
||||
|
||||
@Bean
|
||||
public ConnectionFactory jmsConnectionFactory(JmsProperties properties) throws NamingException {
|
||||
JndiLocatorDelegate jndiLocatorDelegate = JndiLocatorDelegate.createDefaultResourceRefLocator();
|
||||
if (StringUtils.hasLength(properties.getJndiName())) {
|
||||
return jndiLocatorDelegate.lookup(properties.getJndiName(), ConnectionFactory.class);
|
||||
}
|
||||
return findJndiConnectionFactory(jndiLocatorDelegate);
|
||||
}
|
||||
|
||||
private ConnectionFactory findJndiConnectionFactory(JndiLocatorDelegate jndiLocatorDelegate) {
|
||||
for (String name : JNDI_LOCATIONS) {
|
||||
try {
|
||||
return jndiLocatorDelegate.lookup(name, ConnectionFactory.class);
|
||||
}
|
||||
catch (NamingException ex) {
|
||||
// Swallow and continue
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Unable to find ConnectionFactory in JNDI locations " + Arrays.asList(JNDI_LOCATIONS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Condition for JNDI name or a specific property.
|
||||
*/
|
||||
static class JndiOrPropertyCondition extends AnyNestedCondition {
|
||||
|
||||
JndiOrPropertyCondition() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@ConditionalOnJndi({ "java:/JmsXA", "java:/XAConnectionFactory" })
|
||||
static class Jndi {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnProperty("spring.jms.jndi-name")
|
||||
static class Property {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for JMS.
|
||||
*/
|
||||
package org.springframework.boot.jms.autoconfigure;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support for Java Message Service (JMS).
|
||||
*/
|
||||
package org.springframework.boot.jms;
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"groups": [],
|
||||
"properties": [],
|
||||
"hints": [
|
||||
{
|
||||
"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."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.jms.autoconfigure.JmsAutoConfiguration
|
||||
org.springframework.boot.jms.autoconfigure.JndiConnectionFactoryAutoConfiguration
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms;
|
||||
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.messaginghub.pooled.jms.JmsPoolConnectionFactory;
|
||||
|
||||
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
|
||||
import org.springframework.jms.connection.CachingConnectionFactory;
|
||||
import org.springframework.jms.connection.SingleConnectionFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConnectionFactoryUnwrapper}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ConnectionFactoryUnwrapperTests {
|
||||
|
||||
@Nested
|
||||
class UnwrapCaching {
|
||||
|
||||
@Test
|
||||
void unwrapWithSingleConnectionFactory() {
|
||||
ConnectionFactory connectionFactory = new SingleConnectionFactory();
|
||||
assertThat(unwrapCaching(connectionFactory)).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapWithConnectionFactory() {
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
assertThat(unwrapCaching(connectionFactory)).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapWithCachingConnectionFactory() {
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
assertThat(unwrapCaching(new CachingConnectionFactory(connectionFactory))).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapWithNestedCachingConnectionFactories() {
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
CachingConnectionFactory firstCachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
|
||||
CachingConnectionFactory secondCachingConnectionFactory = new CachingConnectionFactory(
|
||||
firstCachingConnectionFactory);
|
||||
assertThat(unwrapCaching(secondCachingConnectionFactory)).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapWithJmsPoolConnectionFactory() {
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
JmsPoolConnectionFactory poolConnectionFactory = new JmsPoolConnectionFactory();
|
||||
poolConnectionFactory.setConnectionFactory(connectionFactory);
|
||||
assertThat(unwrapCaching(poolConnectionFactory)).isSameAs(poolConnectionFactory);
|
||||
}
|
||||
|
||||
private ConnectionFactory unwrapCaching(ConnectionFactory connectionFactory) {
|
||||
return ConnectionFactoryUnwrapper.unwrapCaching(connectionFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Unwrap {
|
||||
|
||||
@Test
|
||||
void unwrapWithSingleConnectionFactory() {
|
||||
ConnectionFactory connectionFactory = new SingleConnectionFactory();
|
||||
assertThat(unwrap(connectionFactory)).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapWithConnectionFactory() {
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
assertThat(unwrap(connectionFactory)).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapWithCachingConnectionFactory() {
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
assertThat(unwrap(new CachingConnectionFactory(connectionFactory))).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapWithNestedCachingConnectionFactories() {
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
CachingConnectionFactory firstCachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
|
||||
CachingConnectionFactory secondCachingConnectionFactory = new CachingConnectionFactory(
|
||||
firstCachingConnectionFactory);
|
||||
assertThat(unwrap(secondCachingConnectionFactory)).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapWithJmsPoolConnectionFactory() {
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
JmsPoolConnectionFactory poolConnectionFactory = new JmsPoolConnectionFactory();
|
||||
poolConnectionFactory.setConnectionFactory(connectionFactory);
|
||||
assertThat(unwrap(poolConnectionFactory)).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapWithNestedJmsPoolConnectionFactories() {
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
JmsPoolConnectionFactory firstPooledConnectionFactory = new JmsPoolConnectionFactory();
|
||||
firstPooledConnectionFactory.setConnectionFactory(connectionFactory);
|
||||
JmsPoolConnectionFactory secondPooledConnectionFactory = new JmsPoolConnectionFactory();
|
||||
secondPooledConnectionFactory.setConnectionFactory(firstPooledConnectionFactory);
|
||||
assertThat(unwrap(secondPooledConnectionFactory)).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ClassPathExclusions("pooled-jms-*")
|
||||
void unwrapWithoutJmsPoolOnClasspath() {
|
||||
assertThat(ClassUtils.isPresent("org.messaginghub.pooled.jms.JmsPoolConnectionFactory", null)).isFalse();
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
assertThat(unwrap(new CachingConnectionFactory(connectionFactory))).isSameAs(connectionFactory);
|
||||
}
|
||||
|
||||
private ConnectionFactory unwrap(ConnectionFactory connectionFactory) {
|
||||
return ConnectionFactoryUnwrapper.unwrap(connectionFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,586 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import jakarta.jms.ExceptionListener;
|
||||
import jakarta.jms.Session;
|
||||
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.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;
|
||||
import org.springframework.jms.config.JmsListenerConfigUtils;
|
||||
import org.springframework.jms.config.JmsListenerContainerFactory;
|
||||
import org.springframework.jms.config.JmsListenerEndpoint;
|
||||
import org.springframework.jms.config.SimpleJmsListenerContainerFactory;
|
||||
import org.springframework.jms.config.SimpleJmsListenerEndpoint;
|
||||
import org.springframework.jms.core.JmsMessagingTemplate;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
import org.springframework.jms.listener.DefaultMessageListenerContainer;
|
||||
import org.springframework.jms.support.converter.MessageConverter;
|
||||
import org.springframework.jms.support.destination.DestinationResolver;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link JmsAutoConfiguration}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Stephane Nicoll
|
||||
* @author Aurélien Leboulanger
|
||||
* @author Eddú Meléndez
|
||||
* @author Vedran Pavic
|
||||
* @author Lasse Wulff
|
||||
*/
|
||||
class JmsAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withBean(ConnectionFactory.class, () -> mock(ConnectionFactory.class))
|
||||
.withConfiguration(AutoConfigurations.of(JmsAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void testNoConnectionFactoryJmsConfiguration() {
|
||||
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(JmsAutoConfiguration.class))
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(JmsTemplate.class)
|
||||
.doesNotHaveBean(JmsMessagingTemplate.class)
|
||||
.doesNotHaveBean(DefaultJmsListenerContainerFactoryConfigurer.class)
|
||||
.doesNotHaveBean(DefaultJmsListenerContainerFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultJmsConfiguration() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration.class).run((context) -> {
|
||||
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
|
||||
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
|
||||
JmsMessagingTemplate messagingTemplate = context.getBean(JmsMessagingTemplate.class);
|
||||
assertThat(jmsTemplate.getConnectionFactory()).isEqualTo(connectionFactory);
|
||||
assertThat(messagingTemplate.getJmsTemplate()).isEqualTo(jmsTemplate);
|
||||
assertThat(context.containsBean("jmsListenerContainerFactory")).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsTemplateBackOff() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration3.class)
|
||||
.run((context) -> assertThat(context.getBean(JmsTemplate.class).getPriority()).isEqualTo(999));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsMessagingTemplateBackOff() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration5.class)
|
||||
.run((context) -> assertThat(context.getBean(JmsMessagingTemplate.class).getDefaultDestinationName())
|
||||
.isEqualTo("fooBar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultJmsListenerConfiguration() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration.class).run((loaded) -> {
|
||||
ConnectionFactory connectionFactory = loaded.getBean(ConnectionFactory.class);
|
||||
assertThat(loaded).hasSingleBean(DefaultJmsListenerContainerFactory.class);
|
||||
DefaultJmsListenerContainerFactory containerFactory = loaded
|
||||
.getBean(DefaultJmsListenerContainerFactory.class);
|
||||
SimpleJmsListenerEndpoint jmsListenerEndpoint = new SimpleJmsListenerEndpoint();
|
||||
jmsListenerEndpoint.setMessageListener((message) -> {
|
||||
});
|
||||
DefaultMessageListenerContainer container = containerFactory.createListenerContainer(jmsListenerEndpoint);
|
||||
assertThat(container.getClientId()).isNull();
|
||||
assertThat(container.getConcurrentConsumers()).isEqualTo(1);
|
||||
assertThat(container.getConnectionFactory()).isSameAs(connectionFactory);
|
||||
assertThat(container.getMaxConcurrentConsumers()).isEqualTo(1);
|
||||
assertThat(container.getSessionAcknowledgeMode()).isEqualTo(Session.AUTO_ACKNOWLEDGE);
|
||||
assertThat(container.isAutoStartup()).isTrue();
|
||||
assertThat(container.isPubSubDomain()).isFalse();
|
||||
assertThat(container.isSubscriptionDurable()).isFalse();
|
||||
assertThat(container).hasFieldOrPropertyWithValue("receiveTimeout", 1000L);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEnableJmsCreateDefaultContainerFactory() {
|
||||
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class)
|
||||
.run((context) -> assertThat(context)
|
||||
.getBean("jmsListenerContainerFactory", JmsListenerContainerFactory.class)
|
||||
.isExactlyInstanceOf(DefaultJmsListenerContainerFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsListenerContainerFactoryBackOff() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration6.class, EnableJmsConfiguration.class)
|
||||
.run((context) -> assertThat(context)
|
||||
.getBean("jmsListenerContainerFactory", JmsListenerContainerFactory.class)
|
||||
.isExactlyInstanceOf(SimpleJmsListenerContainerFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jmsListenerContainerFactoryWhenMultipleConnectionFactoryBeansShouldBackOff() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration10.class)
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(JmsListenerContainerFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsListenerContainerFactoryWithCustomSettings() {
|
||||
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class)
|
||||
.withPropertyValues("spring.jms.listener.autoStartup=false",
|
||||
"spring.jms.listener.session.acknowledgeMode=client",
|
||||
"spring.jms.listener.session.transacted=false", "spring.jms.listener.minConcurrency=2",
|
||||
"spring.jms.listener.receiveTimeout=2s", "spring.jms.listener.maxConcurrency=10",
|
||||
"spring.jms.subscription-durable=true", "spring.jms.client-id=exampleId",
|
||||
"spring.jms.listener.max-messages-per-task=5")
|
||||
.run(this::testJmsListenerContainerFactoryWithCustomSettings);
|
||||
}
|
||||
|
||||
private void testJmsListenerContainerFactoryWithCustomSettings(AssertableApplicationContext loaded) {
|
||||
DefaultMessageListenerContainer container = getContainer(loaded, "jmsListenerContainerFactory");
|
||||
assertThat(container.isAutoStartup()).isFalse();
|
||||
assertThat(container.getSessionAcknowledgeMode()).isEqualTo(Session.CLIENT_ACKNOWLEDGE);
|
||||
assertThat(container.isSessionTransacted()).isFalse();
|
||||
assertThat(container.getConcurrentConsumers()).isEqualTo(2);
|
||||
assertThat(container.getMaxConcurrentConsumers()).isEqualTo(10);
|
||||
assertThat(container).hasFieldOrPropertyWithValue("receiveTimeout", 2000L);
|
||||
assertThat(container).hasFieldOrPropertyWithValue("maxMessagesPerTask", 5);
|
||||
assertThat(container.isSubscriptionDurable()).isTrue();
|
||||
assertThat(container.getClientId()).isEqualTo("exampleId");
|
||||
}
|
||||
|
||||
@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)
|
||||
.run(this::testJmsListenerContainerFactoryWithDefaultSettings);
|
||||
}
|
||||
|
||||
private void testJmsListenerContainerFactoryWithDefaultSettings(AssertableApplicationContext loaded) {
|
||||
DefaultMessageListenerContainer container = getContainer(loaded, "jmsListenerContainerFactory");
|
||||
assertThat(container).hasFieldOrPropertyWithValue("receiveTimeout", 1000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultContainerFactoryWithJtaTransactionManager() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration7.class, EnableJmsConfiguration.class)
|
||||
.run((context) -> {
|
||||
DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory");
|
||||
assertThat(container.isSessionTransacted()).isFalse();
|
||||
assertThat(container).hasFieldOrPropertyWithValue("transactionManager",
|
||||
context.getBean(JtaTransactionManager.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultContainerFactoryWithJtaTransactionManagerAndSessionTransactedEnabled() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration7.class, EnableJmsConfiguration.class)
|
||||
.withPropertyValues("spring.jms.listener.session.transacted=true")
|
||||
.run((context) -> {
|
||||
DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory");
|
||||
assertThat(container.isSessionTransacted()).isTrue();
|
||||
assertThat(container).hasFieldOrPropertyWithValue("transactionManager",
|
||||
context.getBean(JtaTransactionManager.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultContainerFactoryNonJtaTransactionManager() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration8.class, EnableJmsConfiguration.class)
|
||||
.run((context) -> {
|
||||
DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory");
|
||||
assertThat(container.isSessionTransacted()).isTrue();
|
||||
assertThat(container).hasFieldOrPropertyWithValue("transactionManager", null);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultContainerFactoryNoTransactionManager() {
|
||||
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class).run((context) -> {
|
||||
DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory");
|
||||
assertThat(container.isSessionTransacted()).isTrue();
|
||||
assertThat(container).hasFieldOrPropertyWithValue("transactionManager", null);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultContainerFactoryNoTransactionManagerAndSessionTransactedDisabled() {
|
||||
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class)
|
||||
.withPropertyValues("spring.jms.listener.session.transacted=false")
|
||||
.run((context) -> {
|
||||
DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory");
|
||||
assertThat(container.isSessionTransacted()).isFalse();
|
||||
assertThat(container).hasFieldOrPropertyWithValue("transactionManager", null);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultContainerFactoryWithMessageConverters() {
|
||||
this.contextRunner.withUserConfiguration(MessageConvertersConfiguration.class, EnableJmsConfiguration.class)
|
||||
.run((context) -> {
|
||||
DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory");
|
||||
assertThat(container.getMessageConverter()).isSameAs(context.getBean("myMessageConverter"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultContainerFactoryWithExceptionListener() {
|
||||
ExceptionListener exceptionListener = mock(ExceptionListener.class);
|
||||
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class)
|
||||
.withBean(ExceptionListener.class, () -> exceptionListener)
|
||||
.run((context) -> {
|
||||
DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory");
|
||||
assertThat(container.getExceptionListener()).isSameAs(exceptionListener);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultContainerFactoryWithObservationRegistry() {
|
||||
ObservationRegistry observationRegistry = mock(ObservationRegistry.class);
|
||||
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class)
|
||||
.withBean(ObservationRegistry.class, () -> observationRegistry)
|
||||
.run((context) -> {
|
||||
DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory");
|
||||
assertThat(container.getObservationRegistry()).isSameAs(observationRegistry);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomContainerFactoryWithConfigurer() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration9.class, EnableJmsConfiguration.class)
|
||||
.withPropertyValues("spring.jms.listener.autoStartup=false")
|
||||
.run((context) -> {
|
||||
DefaultMessageListenerContainer container = getContainer(context, "customListenerContainerFactory");
|
||||
assertThat(container.getCacheLevel()).isEqualTo(DefaultMessageListenerContainer.CACHE_CONSUMER);
|
||||
assertThat(container.isAutoStartup()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
private DefaultMessageListenerContainer getContainer(AssertableApplicationContext loaded, String name) {
|
||||
JmsListenerContainerFactory<?> factory = loaded.getBean(name, JmsListenerContainerFactory.class);
|
||||
assertThat(factory).isInstanceOf(DefaultJmsListenerContainerFactory.class);
|
||||
return ((DefaultJmsListenerContainerFactory) factory).createListenerContainer(mock(JmsListenerEndpoint.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsTemplateWithMessageConverter() {
|
||||
this.contextRunner.withUserConfiguration(MessageConvertersConfiguration.class).run((context) -> {
|
||||
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
|
||||
assertThat(jmsTemplate.getMessageConverter()).isSameAs(context.getBean("myMessageConverter"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsTemplateWithDestinationResolver() {
|
||||
this.contextRunner.withUserConfiguration(DestinationResolversConfiguration.class)
|
||||
.run((context) -> assertThat(context.getBean(JmsTemplate.class).getDestinationResolver())
|
||||
.isSameAs(context.getBean("myDestinationResolver")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsTemplateWithObservationRegistry() {
|
||||
ObservationRegistry observationRegistry = mock(ObservationRegistry.class);
|
||||
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class)
|
||||
.withBean(ObservationRegistry.class, () -> observationRegistry)
|
||||
.run((context) -> {
|
||||
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
|
||||
assertThat(jmsTemplate).extracting("observationRegistry").isSameAs(observationRegistry);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsTemplateFullCustomization() {
|
||||
this.contextRunner.withUserConfiguration(MessageConvertersConfiguration.class)
|
||||
.withPropertyValues("spring.jms.template.session.acknowledge-mode=client",
|
||||
"spring.jms.template.session.transacted=true", "spring.jms.template.default-destination=testQueue",
|
||||
"spring.jms.template.delivery-delay=500", "spring.jms.template.delivery-mode=non-persistent",
|
||||
"spring.jms.template.priority=6", "spring.jms.template.time-to-live=6000",
|
||||
"spring.jms.template.receive-timeout=2000")
|
||||
.run((context) -> {
|
||||
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
|
||||
assertThat(jmsTemplate.getMessageConverter()).isSameAs(context.getBean("myMessageConverter"));
|
||||
assertThat(jmsTemplate.isPubSubDomain()).isFalse();
|
||||
assertThat(jmsTemplate.getSessionAcknowledgeMode()).isEqualTo(Session.CLIENT_ACKNOWLEDGE);
|
||||
assertThat(jmsTemplate.isSessionTransacted()).isTrue();
|
||||
assertThat(jmsTemplate.getDefaultDestinationName()).isEqualTo("testQueue");
|
||||
assertThat(jmsTemplate.getDeliveryDelay()).isEqualTo(500);
|
||||
assertThat(jmsTemplate.getDeliveryMode()).isOne();
|
||||
assertThat(jmsTemplate.getPriority()).isEqualTo(6);
|
||||
assertThat(jmsTemplate.getTimeToLive()).isEqualTo(6000);
|
||||
assertThat(jmsTemplate.isExplicitQosEnabled()).isTrue();
|
||||
assertThat(jmsTemplate.getReceiveTimeout()).isEqualTo(2000);
|
||||
});
|
||||
}
|
||||
|
||||
@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) -> {
|
||||
JmsMessagingTemplate messagingTemplate = context.getBean(JmsMessagingTemplate.class);
|
||||
assertThat(messagingTemplate.getDefaultDestinationName()).isEqualTo("testQueue");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPubSubDisabledByDefault() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration.class)
|
||||
.run((context) -> assertThat(context.getBean(JmsTemplate.class).isPubSubDomain()).isFalse());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJmsTemplatePostProcessedSoThatPubSubIsTrue() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration4.class)
|
||||
.run((context) -> assertThat(context.getBean(JmsTemplate.class).isPubSubDomain()).isTrue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPubSubDomainActive() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration.class)
|
||||
.withPropertyValues("spring.jms.pubSubDomain:true")
|
||||
.run((context) -> {
|
||||
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
|
||||
DefaultMessageListenerContainer defaultMessageListenerContainer = context
|
||||
.getBean(DefaultJmsListenerContainerFactory.class)
|
||||
.createListenerContainer(mock(JmsListenerEndpoint.class));
|
||||
assertThat(jmsTemplate.isPubSubDomain()).isTrue();
|
||||
assertThat(defaultMessageListenerContainer.isPubSubDomain()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPubSubDomainOverride() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration.class)
|
||||
.withPropertyValues("spring.jms.pubSubDomain:false")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(JmsTemplate.class);
|
||||
assertThat(context).hasSingleBean(ConnectionFactory.class);
|
||||
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
|
||||
ConnectionFactory factory = context.getBean(ConnectionFactory.class);
|
||||
assertThat(jmsTemplate).isNotNull();
|
||||
assertThat(jmsTemplate.isPubSubDomain()).isFalse();
|
||||
assertThat(factory).isNotNull().isEqualTo(jmsTemplate.getConnectionFactory());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableJmsAutomatically() {
|
||||
this.contextRunner.withUserConfiguration(NoEnableJmsConfiguration.class)
|
||||
.run((context) -> assertThat(context)
|
||||
.hasBean(JmsListenerConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
|
||||
.hasBean(JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
void runtimeHintsAreRegisteredForBindingOfAcknowledgeMode() {
|
||||
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
|
||||
context.register(TestConfiguration2.class, JmsAutoConfiguration.class);
|
||||
TestGenerationContext generationContext = new TestGenerationContext();
|
||||
new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(AcknowledgeMode.class, "of"))
|
||||
.accepts(generationContext.getRuntimeHints());
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration2 {
|
||||
|
||||
@Bean
|
||||
ConnectionFactory customConnectionFactory() {
|
||||
return mock(ConnectionFactory.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration3 {
|
||||
|
||||
@Bean
|
||||
JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
|
||||
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
|
||||
jmsTemplate.setPriority(999);
|
||||
return jmsTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration4 implements BeanPostProcessor {
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) {
|
||||
if (bean.getClass().isAssignableFrom(JmsTemplate.class)) {
|
||||
JmsTemplate jmsTemplate = (JmsTemplate) bean;
|
||||
jmsTemplate.setPubSubDomain(true);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration5 {
|
||||
|
||||
@Bean
|
||||
JmsMessagingTemplate jmsMessagingTemplate(JmsTemplate jmsTemplate) {
|
||||
JmsMessagingTemplate messagingTemplate = new JmsMessagingTemplate(jmsTemplate);
|
||||
messagingTemplate.setDefaultDestinationName("fooBar");
|
||||
return messagingTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration6 {
|
||||
|
||||
@Bean
|
||||
JmsListenerContainerFactory<?> jmsListenerContainerFactory(ConnectionFactory connectionFactory) {
|
||||
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
|
||||
factory.setConnectionFactory(connectionFactory);
|
||||
return factory;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration7 {
|
||||
|
||||
@Bean
|
||||
JtaTransactionManager transactionManager() {
|
||||
return mock(JtaTransactionManager.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration8 {
|
||||
|
||||
@Bean
|
||||
DataSourceTransactionManager transactionManager() {
|
||||
return mock(DataSourceTransactionManager.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class MessageConvertersConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
MessageConverter myMessageConverter() {
|
||||
return mock(MessageConverter.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessageConverter anotherMessageConverter() {
|
||||
return mock(MessageConverter.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class DestinationResolversConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
DestinationResolver myDestinationResolver() {
|
||||
return mock(DestinationResolver.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
DestinationResolver anotherDestinationResolver() {
|
||||
return mock(DestinationResolver.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration9 {
|
||||
|
||||
@Bean
|
||||
JmsListenerContainerFactory<?> customListenerContainerFactory(
|
||||
DefaultJmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
|
||||
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
|
||||
configurer.configure(factory, connectionFactory);
|
||||
factory.setCacheLevel(DefaultMessageListenerContainer.CACHE_CONSUMER);
|
||||
return factory;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration10 {
|
||||
|
||||
@Bean
|
||||
ConnectionFactory connectionFactory1() {
|
||||
return mock(ConnectionFactory.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ConnectionFactory connectionFactory2() {
|
||||
return mock(ConnectionFactory.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableJms
|
||||
static class EnableJmsConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class NoEnableJmsConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.jms.listener.AbstractPollingMessageListenerContainer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JmsProperties}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class JmsPropertiesTests {
|
||||
|
||||
@Test
|
||||
void formatConcurrencyNull() {
|
||||
JmsProperties properties = new JmsProperties();
|
||||
assertThat(properties.getListener().formatConcurrency()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatConcurrencyOnlyLowerBound() {
|
||||
JmsProperties properties = new JmsProperties();
|
||||
properties.getListener().setMinConcurrency(2);
|
||||
assertThat(properties.getListener().formatConcurrency()).isEqualTo("2-2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatConcurrencyOnlyHigherBound() {
|
||||
JmsProperties properties = new JmsProperties();
|
||||
properties.getListener().setMaxConcurrency(5);
|
||||
assertThat(properties.getListener().formatConcurrency()).isEqualTo("1-5");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatConcurrencyBothBounds() {
|
||||
JmsProperties properties = new JmsProperties();
|
||||
properties.getListener().setMinConcurrency(2);
|
||||
properties.getListener().setMaxConcurrency(10);
|
||||
assertThat(properties.getListener().formatConcurrency()).isEqualTo("2-10");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setDeliveryModeEnablesQoS() {
|
||||
JmsProperties properties = new JmsProperties();
|
||||
properties.getTemplate().setDeliveryMode(JmsProperties.DeliveryMode.PERSISTENT);
|
||||
assertThat(properties.getTemplate().determineQosEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setPriorityEnablesQoS() {
|
||||
JmsProperties properties = new JmsProperties();
|
||||
properties.getTemplate().setPriority(6);
|
||||
assertThat(properties.getTemplate().determineQosEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setTimeToLiveEnablesQoS() {
|
||||
JmsProperties properties = new JmsProperties();
|
||||
properties.getTemplate().setTimeToLive(Duration.ofSeconds(5));
|
||||
assertThat(properties.getTemplate().determineQosEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultReceiveTimeoutMatchesListenerContainersDefault() {
|
||||
assertThat(new JmsProperties().getListener().getReceiveTimeout())
|
||||
.hasMillis(AbstractPollingMessageListenerContainer.DEFAULT_RECEIVE_TIMEOUT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.jms.autoconfigure;
|
||||
|
||||
import javax.naming.Context;
|
||||
|
||||
import jakarta.jms.ConnectionFactory;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.jndi.JndiPropertiesHidingClassLoader;
|
||||
import org.springframework.boot.autoconfigure.jndi.TestableInitialContextFactory;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link JndiConnectionFactoryAutoConfiguration}.
|
||||
* PersistenceExceptionTranslationAutoConfigurationTests
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class JndiConnectionFactoryAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(JndiConnectionFactoryAutoConfiguration.class));
|
||||
|
||||
private ClassLoader threadContextClassLoader;
|
||||
|
||||
private String initialContextFactory;
|
||||
|
||||
@BeforeEach
|
||||
void setupJndi() {
|
||||
this.initialContextFactory = System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
|
||||
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, TestableInitialContextFactory.class.getName());
|
||||
this.threadContextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(new JndiPropertiesHidingClassLoader(getClass().getClassLoader()));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanUp() {
|
||||
TestableInitialContextFactory.clearAll();
|
||||
if (this.initialContextFactory != null) {
|
||||
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, this.initialContextFactory);
|
||||
}
|
||||
else {
|
||||
System.clearProperty(Context.INITIAL_CONTEXT_FACTORY);
|
||||
}
|
||||
Thread.currentThread().setContextClassLoader(this.threadContextClassLoader);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectNoAvailableCandidates() {
|
||||
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ConnectionFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectWithJmsXAConnectionFactory() {
|
||||
ConnectionFactory connectionFactory = configureConnectionFactory("java:/JmsXA");
|
||||
this.contextRunner.run(assertConnectionFactory(connectionFactory));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectWithXAConnectionFactory() {
|
||||
ConnectionFactory connectionFactory = configureConnectionFactory("java:/XAConnectionFactory");
|
||||
this.contextRunner.run(assertConnectionFactory(connectionFactory));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jndiNamePropertySet() {
|
||||
ConnectionFactory connectionFactory = configureConnectionFactory("java:comp/env/myCF");
|
||||
this.contextRunner.withPropertyValues("spring.jms.jndi-name=java:comp/env/myCF")
|
||||
.run(assertConnectionFactory(connectionFactory));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jndiNamePropertySetWithResourceRef() {
|
||||
ConnectionFactory connectionFactory = configureConnectionFactory("java:comp/env/myCF");
|
||||
this.contextRunner.withPropertyValues("spring.jms.jndi-name=myCF")
|
||||
.run(assertConnectionFactory(connectionFactory));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jndiNamePropertySetWithWrongValue() {
|
||||
this.contextRunner.withPropertyValues("spring.jms.jndi-name=doesNotExistCF").run((context) -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context).getFailure()
|
||||
.isInstanceOf(BeanCreationException.class)
|
||||
.hasMessageContaining("doesNotExistCF");
|
||||
});
|
||||
}
|
||||
|
||||
private ContextConsumer<AssertableApplicationContext> assertConnectionFactory(ConnectionFactory connectionFactory) {
|
||||
return (context) -> {
|
||||
assertThat(context).hasSingleBean(ConnectionFactory.class).hasBean("jmsConnectionFactory");
|
||||
assertThat(context.getBean(ConnectionFactory.class)).isSameAs(connectionFactory)
|
||||
.isSameAs(context.getBean("jmsConnectionFactory"));
|
||||
};
|
||||
}
|
||||
|
||||
private ConnectionFactory configureConnectionFactory(String name) {
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
TestableInitialContextFactory.bind(name, connectionFactory);
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user