Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.springframework.integration.jms.config;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

Expand Down Expand Up @@ -55,6 +56,7 @@
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @author Glenn Renfro
*
* @since 2.0
*/
Expand All @@ -68,7 +70,11 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne

private final boolean messageDriven;

private final JmsTemplate jmsTemplate = new DynamicJmsTemplate();
private JmsTemplate jmsTemplate = new DynamicJmsTemplate();

private boolean jmsTemplateExplicitlySet;

private final List<String> templateDelegatingOptions = new ArrayList<>();

private @Nullable Class<? extends AbstractMessageListenerContainer> containerType;

Expand Down Expand Up @@ -164,30 +170,37 @@ public void setInterceptors(List<ChannelInterceptor> interceptors) {
*/

public void setDeliveryPersistent(boolean deliveryPersistent) {
recordJmsTemplateOption("deliveryPersistent");
this.jmsTemplate.setDeliveryPersistent(deliveryPersistent);
}

public void setExplicitQosEnabled(boolean explicitQosEnabled) {
recordJmsTemplateOption("explicitQosEnabled");
this.jmsTemplate.setExplicitQosEnabled(explicitQosEnabled);
}

public void setMessageConverter(MessageConverter messageConverter) {
recordJmsTemplateOption("messageConverter");
this.jmsTemplate.setMessageConverter(messageConverter);
}

public void setMessageIdEnabled(boolean messageIdEnabled) {
recordJmsTemplateOption("messageIdEnabled");
this.jmsTemplate.setMessageIdEnabled(messageIdEnabled);
}

public void setMessageTimestampEnabled(boolean messageTimestampEnabled) {
recordJmsTemplateOption("messageTimestampEnabled");
this.jmsTemplate.setMessageTimestampEnabled(messageTimestampEnabled);
}

public void setPriority(int priority) {
recordJmsTemplateOption("priority");
this.jmsTemplate.setPriority(priority);
}

public void setTimeToLive(long timeToLive) {
recordJmsTemplateOption("timeToLive");
this.jmsTemplate.setTimeToLive(timeToLive);
}

Expand Down Expand Up @@ -236,6 +249,7 @@ public void setConcurrentConsumers(int concurrentConsumers) {

public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
recordJmsTemplateOption("connectionFactory");
this.jmsTemplate.setConnectionFactory(this.connectionFactory);
}

Expand All @@ -254,6 +268,7 @@ public void setDestinationName(String destinationName) {

public void setDestinationResolver(DestinationResolver destinationResolver) {
this.destinationResolver = destinationResolver;
recordJmsTemplateOption("destinationResolver");
this.jmsTemplate.setDestinationResolver(destinationResolver);
}

Expand Down Expand Up @@ -302,16 +317,19 @@ public void setPhase(int phase) {

public void setPubSubDomain(boolean pubSubDomain) {
this.pubSubDomain = pubSubDomain;
recordJmsTemplateOption("pubSubDomain");
this.jmsTemplate.setPubSubDomain(pubSubDomain);
}

public void setPubSubNoLocal(boolean pubSubNoLocal) {
this.pubSubNoLocal = pubSubNoLocal;
recordJmsTemplateOption("pubSubNoLocal");
this.jmsTemplate.setPubSubNoLocal(pubSubNoLocal);
}

public void setReceiveTimeout(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
recordJmsTemplateOption("receiveTimeout");
this.jmsTemplate.setReceiveTimeout(receiveTimeout);
}

Expand All @@ -322,11 +340,30 @@ public void setRecoveryInterval(long recoveryInterval) {

public void setSessionAcknowledgeMode(int sessionAcknowledgeMode) {
this.sessionAcknowledgeMode = sessionAcknowledgeMode;
recordJmsTemplateOption("sessionAcknowledgeMode");
this.jmsTemplate.setSessionAcknowledgeMode(sessionAcknowledgeMode);
}

/**
* The template to be used by the Factory.
* When an external {@link JmsTemplate} is provided, none of the individual template options
* (e.g. {@code deliveryPersistent}, {@code connectionFactory}, {@code receiveTimeout}, etc.)
* may be set on this factory bean — configure them directly on the provided template instead.
* @param jmsTemplate the {@link JmsTemplate} to build on
* @since 7.1
*/
public void setJmsTemplate(JmsTemplate jmsTemplate) {
Comment thread
artembilan marked this conversation as resolved.
this.jmsTemplate = jmsTemplate;
this.jmsTemplateExplicitlySet = true;
}

private void recordJmsTemplateOption(String option) {
this.templateDelegatingOptions.add(option);
}

public void setSessionTransacted(boolean sessionTransacted) {
this.sessionTransacted = sessionTransacted;
recordJmsTemplateOption("sessionTransacted");
this.jmsTemplate.setSessionTransacted(sessionTransacted);
}

Expand Down Expand Up @@ -406,6 +443,15 @@ protected AbstractJmsChannel createInstance() {
private void initializeJmsTemplate() {
Assert.isTrue(this.destination != null ^ this.destinationName != null,
"Exactly one of destination or destinationName is required.");
Assert.isTrue(!this.jmsTemplateExplicitlySet || this.templateDelegatingOptions.isEmpty(),
() -> {
String quoted = this.templateDelegatingOptions.stream()
.map(opt -> "'" + opt + "'")
.reduce((a, b) -> a + ", " + b)
.orElse("");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting, but I think we don't need to track options being set.
Let's just say straight in this error message what options must be set on the template instead if this factory bean!
I mean if at least one of those properties is sent, we set respective boolean flag and check it here.
Then if both for those are true we raise an exception.

return "The following options must be provided on the externally configured JmsTemplate " +
"instead of on this factory bean: " + quoted;
});
JavaUtils.INSTANCE
.acceptIfNotNull(this.destination, this.jmsTemplate::setDefaultDestination)
.acceptIfNotNull(this.destinationName, this.jmsTemplate::setDefaultDestinationName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* @author Artem Bilan
* @author Gary Russell
* @author Artem Vozhdayenko
* @author Glenn Renfro
*
* @since 5.0
*/
Expand All @@ -46,6 +47,18 @@ public static JmsPollableMessageChannelSpec<?, PollableJmsChannel> pollableChann
return new JmsPollableMessageChannelSpec<>(connectionFactory);
}

/**
* The factory to produce a {@link JmsPollableMessageChannelSpec}.
* @param jmsTemplate the {@link JmsTemplate} to build on
* @return the {@link JmsPollableMessageChannelSpec} instance
* @since 7.1
*/
public static JmsPollableMessageChannelSpec<?, PollableJmsChannel> pollableChannel(
JmsTemplate jmsTemplate) {

return new JmsPollableMessageChannelSpec<>(jmsTemplate);
}

/**
* The factory to produce a {@link JmsPollableMessageChannelSpec}.
* @param id the bean name for the target {@code PollableChannel} component
Expand All @@ -60,6 +73,20 @@ public static JmsPollableMessageChannelSpec<?, PollableJmsChannel> pollableChann
return spec.id(id);
}

/**
* The template to produce a {@link JmsPollableMessageChannelSpec}.
* @param id the bean name for the target {@code PollableChannel} component
* @param jmsTemplate the {@link JmsTemplate} to build on
* @return the {@link JmsPollableMessageChannelSpec} instance
* @since 7.1
*/
public static JmsPollableMessageChannelSpec<?, PollableJmsChannel> pollableChannel(String id,
JmsTemplate jmsTemplate) {

JmsPollableMessageChannelSpec<?, PollableJmsChannel> spec = new JmsPollableMessageChannelSpec<>(jmsTemplate);
return spec.id(id);
}

/**
* The factory to produce a {@link JmsMessageChannelSpec}.
* @param connectionFactory the JMS ConnectionFactory to build on
Expand All @@ -69,6 +96,16 @@ public static JmsPollableMessageChannelSpec<?, PollableJmsChannel> pollableChann
return new JmsMessageChannelSpec<>(connectionFactory);
}

/**
* The factory to produce a {@link JmsMessageChannelSpec}.
* @param jmsTemplate the {@link JmsTemplate} to build on
* @return the {@link JmsMessageChannelSpec} instance
* @since 7.1
*/
public static JmsMessageChannelSpec<?, ?> channel(JmsTemplate jmsTemplate) {
return new JmsMessageChannelSpec<>(jmsTemplate);
}

/**
* The factory to produce a {@link JmsMessageChannelSpec}.
* @param id the bean name for the target {@code MessageChannel} component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.springframework.integration.jms.channel.AbstractJmsChannel;
import org.springframework.integration.jms.channel.SubscribableJmsChannel;
import org.springframework.integration.jms.config.JmsChannelFactoryBean;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.listener.AbstractMessageListenerContainer;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.ErrorHandler;
Expand All @@ -37,6 +38,7 @@
* @author Artem Bilan
* @author Gary Russell
* @author Artem Vozhdayenko
* @author Glenn Renfro
*
* @since 5.0
*/
Expand All @@ -47,6 +49,10 @@ protected JmsMessageChannelSpec(ConnectionFactory connectionFactory) {
super(new JmsChannelFactoryBean(true), connectionFactory);
}

protected JmsMessageChannelSpec(JmsTemplate jmsTemplate) {
super(new JmsChannelFactoryBean(true), jmsTemplate);
}

/**
* Configure the type of the container.
* {@link AbstractMessageListenerContainer}. Defaults to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.springframework.integration.dsl.MessageChannelSpec;
import org.springframework.integration.jms.channel.AbstractJmsChannel;
import org.springframework.integration.jms.config.JmsChannelFactoryBean;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.destination.DestinationResolver;

Expand All @@ -36,6 +37,7 @@
* @author Artem Bilan
* @author Gary Russell
* @author Artem Vozhdayenko
* @author Glenn Renfro
*
* @since 5.0
*/
Expand All @@ -48,13 +50,22 @@ protected JmsPollableMessageChannelSpec(ConnectionFactory connectionFactory) {
this(new JmsChannelFactoryBean(false), connectionFactory);
}

protected JmsPollableMessageChannelSpec(JmsTemplate jmsTemplate) {
this(new JmsChannelFactoryBean(false), jmsTemplate);
}

protected JmsPollableMessageChannelSpec(JmsChannelFactoryBean jmsChannelFactoryBean,
ConnectionFactory connectionFactory) {

this.jmsChannelFactoryBean = jmsChannelFactoryBean;
this.jmsChannelFactoryBean.setConnectionFactory(connectionFactory);
this.jmsChannelFactoryBean.setSingleton(false);
this.jmsChannelFactoryBean.setBeanFactory(new DefaultListableBeanFactory());
}

protected JmsPollableMessageChannelSpec(JmsChannelFactoryBean jmsChannelFactoryBean,
JmsTemplate jmsTemplate) {

this.jmsChannelFactoryBean = jmsChannelFactoryBean;
this.jmsChannelFactoryBean.setJmsTemplate(jmsTemplate);
}

@Override
Expand Down Expand Up @@ -220,6 +231,8 @@ public S sessionTransacted(boolean sessionTransacted) {
@SuppressWarnings("unchecked")
protected T doGet() {
try {
this.jmsChannelFactoryBean.setSingleton(false);
this.jmsChannelFactoryBean.setBeanFactory(new DefaultListableBeanFactory());
this.channel = (T) this.jmsChannelFactoryBean.getObject();
}
catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2026-present 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.integration.jms.config;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.support.StaticListableBeanFactory;
import org.springframework.jms.core.JmsTemplate;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;

/**
* @author Glenn Renfro
*/

class JmsChannelFactoryBeanTests {

@Test
void optionSetBeforeExternalTemplateAlsoThrows() {
JmsChannelFactoryBean fb = new JmsChannelFactoryBean(false);
fb.setBeanName("testChannel");
fb.setDestinationName("testQueue");
fb.setSessionTransacted(true);
fb.setJmsTemplate(new JmsTemplate());

assertThatIllegalArgumentException()
.isThrownBy(fb::createInstance)
.withMessageContaining("'sessionTransacted'")
.withMessageContaining("must be provided on the externally configured JmsTemplate");
}

@Test
void externalTemplateAloneDoesNotThrowGuard() {
JmsChannelFactoryBean fb = new JmsChannelFactoryBean(false);
fb.setBeanName("testChannel");
fb.setJmsTemplate(new JmsTemplate());
fb.setDestinationName("testQueue");
fb.setBeanFactory(new StaticListableBeanFactory());
assertThat(fb.createInstance()).isNotNull();
}

}
Loading