INT-4317: JMS: dynamic deliverMode and timeToLive (#2561)

* INT-4317: JMS: dynamic deliverMode and timeToLive

JIRA: https://jira.spring.io/browse/INT-4317

* Add `deliveryModeExpression` and `timeToLiveExpression` properties
to the `JmsSendingMessageHandler` and expose them in the Java DSL and
XML components
* Add `setMapInboundDeliveryMode()` and `setMapInboundExpiration()`
`boolean` properties (default `false`) to the `DefaultJmsHeaderMapper`
for transferring `JMSDeliveryMode` and `JMSExpiration` into appropriate
`JmsHeaders.DELIVERY_MODE` and `JmsHeaders.EXPIRATION` headers
* Upgrade to latest Kotlin and AssertK

* Upgrade to Kotlin 1.2.61
This commit is contained in:
Artem Bilan
2018-09-11 16:16:32 -04:00
committed by Gary Russell
parent 21338d1934
commit 90e4c54210
14 changed files with 361 additions and 55 deletions

View File

@@ -1,5 +1,5 @@
buildscript {
ext.kotlinVersion = '1.2.51'
ext.kotlinVersion = '1.2.61'
repositories {
maven { url 'https://repo.spring.io/plugins-release' }
}
@@ -88,7 +88,7 @@ subprojects { subproject ->
apacheSshdVersion = '1.7.0'
aspectjVersion = '1.9.0'
assertjVersion = '3.9.1'
assertkVersion = '0.10'
assertkVersion = '0.12'
boonVersion = '0.34'
commonsDbcp2Version = '2.2.0'
commonsIoVersion = '2.6'
@@ -178,9 +178,7 @@ subprojects { subproject ->
testRuntime "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion"
testRuntime "org.apache.logging.log4j:log4j-jcl:$log4jVersion"
testCompile("com.willowtreeapps.assertk:assertk:$assertkVersion") {
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-reflect'
}
testCompile("com.willowtreeapps.assertk:assertk-jvm:$assertkVersion")
testCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
testRuntime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@@ -68,15 +68,35 @@ public class DefaultJmsHeaderMapper extends JmsHeaderMapper {
private volatile boolean mapInboundPriority = true;
private volatile boolean mapInboundDeliveryMode = false;
private volatile boolean mapInboundExpiration = false;
/**
* Suppress the mapping of inbound priority by using this setter with 'false'.
*
* @param mapInboundPriority 'false' to suppress mapping the inbound priority.
*/
public void setMapInboundPriority(boolean mapInboundPriority) {
this.mapInboundPriority = mapInboundPriority;
}
/**
* Map the inbound {@code deliveryMode} by using this setter with 'true'.
* @param mapInboundDeliveryMode 'true' to map the inbound delivery mode.
* @since 5.1
*/
public void setMapInboundDeliveryMode(boolean mapInboundDeliveryMode) {
this.mapInboundDeliveryMode = mapInboundDeliveryMode;
}
/**
* Map the inbound {@code expiration} by using this setter with 'true'.
* @param mapInboundExpiration 'true' to map the inbound expiration.
* @since 5.1
*/
public void setMapInboundExpiration(boolean mapInboundExpiration) {
this.mapInboundExpiration = mapInboundExpiration;
}
/**
* Specify a prefix to be appended to the integration message header name
* for any JMS property that is being mapped into the MessageHeaders.
@@ -248,6 +268,22 @@ public class DefaultJmsHeaderMapper extends JmsHeaderMapper {
this.logger.info("failed to read JMSPriority property, skipping", e);
}
}
if (this.mapInboundDeliveryMode) {
try {
headers.put(JmsHeaders.DELIVERY_MODE, jmsMessage.getJMSDeliveryMode());
}
catch (Exception e) {
this.logger.info("failed to read JMSDeliveryMode property, skipping", e);
}
}
if (this.mapInboundExpiration) {
try {
headers.put(JmsHeaders.EXPIRATION, jmsMessage.getJMSExpiration());
}
catch (Exception e) {
this.logger.info("failed to read JMSExpiration property, skipping", e);
}
}
Enumeration<?> jmsPropertyNames = jmsMessage.getPropertyNames();
if (jmsPropertyNames != null) {
while (jmsPropertyNames.hasMoreElements()) {

View File

@@ -71,4 +71,16 @@ public class DynamicJmsTemplate extends JmsTemplate {
return (receiveTimeout != null) ? receiveTimeout : super.getReceiveTimeout();
}
@Override
public int getDeliveryMode() {
Integer deliveryMode = DynamicJmsTemplateProperties.getDeliveryMode();
return (deliveryMode != null) ? deliveryMode : super.getDeliveryMode();
}
@Override
public long getTimeToLive() {
Long timeToLive = DynamicJmsTemplateProperties.getTimeToLive();
return (timeToLive != null) ? timeToLive : super.getTimeToLive();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2018 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.
@@ -18,14 +18,23 @@ package org.springframework.integration.jms;
/**
* @author Mark Fisher
* @author Artem Bilan
*
* @since 2.0.2
*/
abstract class DynamicJmsTemplateProperties {
private static final ThreadLocal<Integer> priorityHolder = new ThreadLocal<Integer>();
private static final ThreadLocal<Integer> priorityHolder = new ThreadLocal<>();
private static final ThreadLocal<Long> receiveTimeoutHolder = new ThreadLocal<Long>();
private static final ThreadLocal<Long> receiveTimeoutHolder = new ThreadLocal<>();
private static final ThreadLocal<Integer> deliverModeHolder = new ThreadLocal<>();
private static final ThreadLocal<Long> timeToLiveHolder = new ThreadLocal<>();
private DynamicJmsTemplateProperties() {
}
public static Integer getPriority() {
return priorityHolder.get();
@@ -51,4 +60,28 @@ abstract class DynamicJmsTemplateProperties {
receiveTimeoutHolder.remove();
}
public static Integer getDeliveryMode() {
return deliverModeHolder.get();
}
public static void setDeliveryMode(Integer deliveryMode) {
deliverModeHolder.set(deliveryMode);
}
public static void clearDeliveryMode() {
deliverModeHolder.remove();
}
public static Long getTimeToLive() {
return timeToLiveHolder.get();
}
public static void setTimeToLive(Long timeToLive) {
timeToLiveHolder.set(timeToLive);
}
public static void clearTimeToLive() {
timeToLiveHolder.remove();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -17,11 +17,12 @@
package org.springframework.integration.jms;
import javax.jms.Destination;
import javax.jms.JMSException;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.StaticMessageHeaderAccessor;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
import org.springframework.jms.core.JmsTemplate;
@@ -35,20 +36,27 @@ import org.springframework.util.Assert;
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
*/
public class JmsSendingMessageHandler extends AbstractMessageHandler {
private final JmsTemplate jmsTemplate;
private volatile Destination destination;
private Destination destination;
private volatile String destinationName;
private String destinationName;
private volatile JmsHeaderMapper headerMapper = new DefaultJmsHeaderMapper();
private JmsHeaderMapper headerMapper = new DefaultJmsHeaderMapper();
private volatile boolean extractPayload = true;
private boolean extractPayload = true;
private volatile ExpressionEvaluatingMessageProcessor<?> destinationExpressionProcessor;
private ExpressionEvaluatingMessageProcessor<?> destinationExpressionProcessor;
private Expression deliveryModeExpression;
private Expression timeToLiveExpression;
private EvaluationContext evaluationContext;
public JmsSendingMessageHandler(JmsTemplate jmsTemplate) {
@@ -75,22 +83,61 @@ public class JmsSendingMessageHandler extends AbstractMessageHandler {
}
public void setHeaderMapper(JmsHeaderMapper headerMapper) {
Assert.notNull(headerMapper, "'headerMapper' cannot be null");
this.headerMapper = headerMapper;
}
/**
* Specify whether the payload should be extracted from each integration
* Message to be used as the JMS Message body.
*
* <p>The default value is <code>true</code>. To force passing of the full
* Spring Integration Message instead, set this to <code>false</code>.
*
* @param extractPayload true to extract the payload.
*/
public void setExtractPayload(boolean extractPayload) {
this.extractPayload = extractPayload;
}
/**
* Specify a SpEL expression to evaluate a {@code deliveryMode} for the JMS message to send.
* This option is applied only of QoS is enabled on the {@link JmsTemplate}.
* @param deliveryModeExpression to use
* @since 5.1
* @see #setDeliveryModeExpression(Expression)
*/
public void setDeliveryModeExpressionString(String deliveryModeExpression) {
setDeliveryModeExpression(EXPRESSION_PARSER.parseExpression(deliveryModeExpression));
}
/**
* Specify a SpEL expression to evaluate a {@code deliveryMode} for the JMS message to send.
* This option is applied only of QoS is enabled on the {@link JmsTemplate}.
* @param deliveryModeExpression to use
* @since 5.1
*/
public void setDeliveryModeExpression(Expression deliveryModeExpression) {
this.deliveryModeExpression = deliveryModeExpression;
}
/**
* Specify a SpEL expression to evaluate a {@code timeToLive} for the JMS message to send.
* @param timeToLiveExpression to use
* @since 5.1
* @see #setTimeToLiveExpression(Expression)
*/
public void setTimeToLiveExpressionString(String timeToLiveExpression) {
setTimeToLiveExpression(EXPRESSION_PARSER.parseExpression(timeToLiveExpression));
}
/**
* Specify a SpEL expression to evaluate a {@code timeToLive} for the JMS message to send.
* @param timeToLiveExpression to use
* @since 5.1
*/
public void setTimeToLiveExpression(Expression timeToLiveExpression) {
this.timeToLiveExpression = timeToLiveExpression;
}
@Override
public String getComponentType() {
return "jms:outbound-channel-adapter";
@@ -105,22 +152,42 @@ public class JmsSendingMessageHandler extends AbstractMessageHandler {
this.destinationExpressionProcessor.setConversionService(conversionService);
}
}
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
}
@Override
protected void handleMessageInternal(final Message<?> message) throws Exception {
if (message == null) {
throw new IllegalArgumentException("message must not be null");
}
protected void handleMessageInternal(final Message<?> message) {
Object destination = this.determineDestination(message);
Object objectToSend = (this.extractPayload) ? message.getPayload() : message;
MessagePostProcessor messagePostProcessor = new HeaderMappingMessagePostProcessor(message, this.headerMapper);
if (this.jmsTemplate instanceof DynamicJmsTemplate && this.jmsTemplate.isExplicitQosEnabled()) {
Integer priority = StaticMessageHeaderAccessor.getPriority(message);
if (priority != null) {
DynamicJmsTemplateProperties.setPriority(priority);
}
if (this.deliveryModeExpression != null) {
Integer deliveryMode =
this.deliveryModeExpression.getValue(this.evaluationContext, message, Integer.class);
if (deliveryMode != null) {
DynamicJmsTemplateProperties.setDeliveryMode(deliveryMode);
}
}
if (this.timeToLiveExpression != null) {
Long timeToLive = this.timeToLiveExpression.getValue(this.evaluationContext, message, Long.class);
if (timeToLive != null) {
DynamicJmsTemplateProperties.setTimeToLive(timeToLive);
}
}
}
try {
DynamicJmsTemplateProperties.setPriority(new IntegrationMessageHeaderAccessor(message).getPriority());
this.send(destination, objectToSend, messagePostProcessor);
send(destination, objectToSend, messagePostProcessor);
}
finally {
DynamicJmsTemplateProperties.clearPriority();
DynamicJmsTemplateProperties.clearDeliveryMode();
DynamicJmsTemplateProperties.clearTimeToLive();
}
}
@@ -167,10 +234,11 @@ public class JmsSendingMessageHandler extends AbstractMessageHandler {
}
@Override
public javax.jms.Message postProcessMessage(javax.jms.Message jmsMessage) throws JMSException {
public javax.jms.Message postProcessMessage(javax.jms.Message jmsMessage) {
this.headerMapper.fromHeaders(this.integrationMessage.getHeaders(), jmsMessage);
return jmsMessage;
}
}
}

View File

@@ -57,8 +57,10 @@ public class JmsOutboundChannelAdapterParser extends AbstractOutboundChannelAdap
if (hasDestinationRef || hasDestinationName || hasDestinationExpression) {
if (!(hasDestinationRef ^ hasDestinationName ^ hasDestinationExpression)) {
parserContext.getReaderContext().error("The 'destination', 'destination-name', and " +
"'destination-expression' attributes are mutually exclusive.", parserContext.extractSource(element));
parserContext.getReaderContext()
.error("The 'destination', 'destination-name', and 'destination-expression' " +
"attributes are mutually exclusive.",
parserContext.extractSource(element));
}
if (hasDestinationRef) {
builder.addPropertyReference(JmsParserUtils.DESTINATION_PROPERTY, destination);
@@ -66,22 +68,30 @@ public class JmsOutboundChannelAdapterParser extends AbstractOutboundChannelAdap
else if (hasDestinationName) {
builder.addPropertyValue(JmsParserUtils.DESTINATION_NAME_PROPERTY, destinationName);
}
else if (hasDestinationExpression) {
BeanDefinitionBuilder expressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class);
expressionBuilder.addConstructorArgValue(destinationExpression);
builder.addPropertyValue(JmsParserUtils.DESTINATION_EXPRESSION_PROPERTY, expressionBuilder.getBeanDefinition());
else {
BeanDefinitionBuilder expressionBuilder =
BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class)
.addConstructorArgValue(destinationExpression);
builder.addPropertyValue(JmsParserUtils.DESTINATION_EXPRESSION_PROPERTY,
expressionBuilder.getBeanDefinition());
}
}
else if (!hasJmsTemplate) {
parserContext.getReaderContext().error("either a '" + JmsParserUtils.JMS_TEMPLATE_ATTRIBUTE +
"' or one of '" + JmsParserUtils.DESTINATION_ATTRIBUTE + "', '"
+ JmsParserUtils.DESTINATION_NAME_ATTRIBUTE + "', or '" +
JmsParserUtils.DESTINATION_EXPRESSION_ATTRIBUTE +
"' attributes must be provided", parserContext.extractSource(element));
parserContext.getReaderContext()
.error("either a '" + JmsParserUtils.JMS_TEMPLATE_ATTRIBUTE +
"' or one of '" + JmsParserUtils.DESTINATION_ATTRIBUTE + "', '"
+ JmsParserUtils.DESTINATION_NAME_ATTRIBUTE + "', or '" +
JmsParserUtils.DESTINATION_EXPRESSION_ATTRIBUTE +
"' attributes must be provided", parserContext.extractSource(element));
}
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, JmsParserUtils.HEADER_MAPPER_ATTRIBUTE);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,
JmsParserUtils.HEADER_MAPPER_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "delivery-mode-expression",
"deliveryModeExpressionString");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "time-to-live-expression",
"timeToLiveExpressionString");
return builder.getBeanDefinition();
}

View File

@@ -193,6 +193,17 @@ public final class Jms {
}
}
/**
* The factory to produce a {@link JmsMessageDrivenChannelAdapterSpec}.
* @param jmsListenerContainerSpec the {@link JmsListenerContainerSpec} to build on
* @return the {@link JmsMessageDrivenChannelAdapterSpec} instance
*/
public static JmsMessageDrivenChannelAdapterSpec<?> messageDrivenChannelAdapter(
JmsListenerContainerSpec<?, ? extends AbstractMessageListenerContainer> jmsListenerContainerSpec) {
return new JmsMessageDrivenChannelAdapterSpec<>(jmsListenerContainerSpec.get());
}
/**
* The factory to produce a {@link JmsMessageDrivenChannelAdapterSpec}.
* @param listenerContainer the {@link AbstractMessageListenerContainer} to build on

View File

@@ -123,7 +123,53 @@ public class JmsOutboundChannelAdapterSpec<S extends JmsOutboundChannelAdapterSp
* @see FunctionExpression
*/
public <P> S destination(Function<Message<P>, ?> destinationFunction) {
this.target.setDestinationExpression(new FunctionExpression<Message<P>>(destinationFunction));
this.target.setDestinationExpression(new FunctionExpression<>(destinationFunction));
return _this();
}
/**
* Specify a SpEL expression to evaluate a {@code deliveryMode} for JMS message to send.
* @param deliveryModeExpression to use
* @return the spec
* @since 5.1
*/
public S deliveryModeExpression(String deliveryModeExpression) {
this.target.setDeliveryModeExpressionString(deliveryModeExpression);
return _this();
}
/**
* Specify a {@link Function} to resolve a {@code deliveryMode} for JMS message to send.
* @param deliveryModeFunction to use
* @return the spec
* @since 5.1
* @see FunctionExpression
*/
public <P> S deliveryModeFunction(Function<Message<P>, ?> deliveryModeFunction) {
this.target.setDeliveryModeExpression(new FunctionExpression<>(deliveryModeFunction));
return _this();
}
/**
* Specify a SpEL expression to evaluate a {@code timeToLive} for JMS message to send.
* @param timeToLiveExpression to use
* @return the spec
* @since 5.1
*/
public S timeToLiveExpression(String timeToLiveExpression) {
this.target.setTimeToLiveExpressionString(timeToLiveExpression);
return _this();
}
/**
* Specify a {@link Function} to resolve a {@code timeToLive} for JMS message to send.
* @param timeToLiveFunction to use
* @return the spec
* @see FunctionExpression
* @since 5.1
*/
public <P> S timeToLiveFunction(Function<Message<P>, ?> timeToLiveFunction) {
this.target.setTimeToLiveExpression(new FunctionExpression<>(timeToLiveFunction));
return _this();
}

View File

@@ -1259,6 +1259,28 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="delivery-mode-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A SpEL expression to be evaluated at runtime against each Spring Integration Message as
the root object. The result should be an integer representing the delivery mode.
If returns null, falls back to the 'delivery-persistent' or default.
The explicit-qos-enabled has to be enabled.
Note: the static delivery mode value can be specified on the JmsTemplate.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="time-to-live-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A SpEL expression to be evaluated at runtime against each Spring Integration Message as
the root object. The result should be a long representing the time-to-live for the message.
If returns null, falls back to the 'time-to-live' or default.
The 'explicit-qos-enabled' has to be enabled.
Note: the static time-to-live value can be specified on the JmsTemplate.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@@ -41,6 +41,7 @@ import org.springframework.messaging.support.GenericMessage;
/**
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
*/
public class JmsOutboundChannelAdapterParserTests {
@@ -134,13 +135,16 @@ public class JmsOutboundChannelAdapterParserTests {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"jmsOutboundWithJmsTemplateQos.xml", this.getClass());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("adapter");
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(new DirectFieldAccessor(endpoint).getPropertyValue("handler"));
Object handler = new DirectFieldAccessor(endpoint).getPropertyValue("handler");
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
JmsTemplate jmsTemplate = (JmsTemplate) handlerAccessor.getPropertyValue("jmsTemplate");
assertNotNull(jmsTemplate);
assertEquals(context.getBean("template"), jmsTemplate);
assertTrue(jmsTemplate.isExplicitQosEnabled());
assertEquals(7, jmsTemplate.getPriority());
assertEquals(12345, jmsTemplate.getTimeToLive());
assertEquals("1", TestUtils.getPropertyValue(handler, "deliveryModeExpression.expression", String.class));
assertEquals("100", TestUtils.getPropertyValue(handler, "timeToLiveExpression.expression", String.class));
context.close();
}

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:jms="http://www.springframework.org/schema/integration/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:jms="http://www.springframework.org/schema/integration/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
@@ -12,9 +12,11 @@
<integration:channel id="input"/>
<jms:outbound-channel-adapter id="adapter" channel="input" jms-template="template"/>
<jms:outbound-channel-adapter id="adapter" channel="input" jms-template="template"
delivery-mode-expression="1"
time-to-live-expression="100"/>
<bean id="template" class="org.springframework.jms.core.JmsTemplate" >
<bean id="template" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory">
<bean class="org.springframework.jms.connection.SingleConnectionFactory">
<constructor-arg>

View File

@@ -16,7 +16,10 @@
package org.springframework.integration.jms.dsl
import assertk.assert
import assertk.assertions.contains
import assertk.assertions.isEqualTo
import assertk.assertions.isGreaterThan
import assertk.assertions.isNotNull
import org.apache.activemq.ActiveMQConnectionFactory
import org.junit.jupiter.api.Test
@@ -24,17 +27,21 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.integration.IntegrationMessageHeaderAccessor
import org.springframework.integration.config.EnableIntegration
import org.springframework.integration.dsl.IntegrationFlow
import org.springframework.integration.dsl.IntegrationFlows
import org.springframework.integration.dsl.MessageChannels
import org.springframework.integration.jms.DefaultJmsHeaderMapper
import org.springframework.integration.support.MessageBuilder
import org.springframework.jms.support.JmsHeaders
import org.springframework.messaging.MessageChannel
import org.springframework.messaging.PollableChannel
import org.springframework.messaging.simp.SimpMessageHeaderAccessor
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
import java.util.concurrent.Executors
import javax.jms.DeliveryMode
/**
* @author Artem Bilan
@@ -57,15 +64,24 @@ class JmsDslKotlinTests {
this.jmsOutboundInboundChannel.send(MessageBuilder.withPayload(" foo ")
.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "containerSpecDestination")
.setPriority(9)
.build())
val receive = this.jmsOutboundInboundReplyChannel.receive(10000)
val payload = receive?.payload
assertk.assert(payload).isNotNull {
assert(payload).isNotNull {
it.isEqualTo("foo")
}
assert(receive?.headers).isNotNull {
it.contains(IntegrationMessageHeaderAccessor.PRIORITY, 9)
it.contains(JmsHeaders.DELIVERY_MODE, 1)
}
val expiration = receive!!.headers[JmsHeaders.EXPIRATION] as Long
assert(expiration).isGreaterThan(System.currentTimeMillis())
}
@Configuration
@@ -83,24 +99,35 @@ class JmsDslKotlinTests {
fun jmsOutboundFlow() =
IntegrationFlow { f ->
f.handle(Jms.outboundAdapter(jmsConnectionFactory())
.destinationExpression("headers." + SimpMessageHeaderAccessor.DESTINATION_HEADER))
.destinationExpression("headers." + SimpMessageHeaderAccessor.DESTINATION_HEADER)
.deliveryModeFunction<Any> { _ -> DeliveryMode.NON_PERSISTENT }
.timeToLiveExpression("10000")
.configureJmsTemplate { t -> t.explicitQosEnabled(true) })
}
@Bean
fun jmsHeaderMapper(): DefaultJmsHeaderMapper {
val jmsHeaderMapper = DefaultJmsHeaderMapper()
jmsHeaderMapper.setMapInboundDeliveryMode(true)
jmsHeaderMapper.setMapInboundExpiration(true)
return jmsHeaderMapper
}
@Bean
fun jmsOutboundInboundReplyChannel() = MessageChannels.queue().get()
@Bean
fun jmsMessageDrivenFlowWithContainer() =
IntegrationFlows.from(
Jms.messageDrivenChannelAdapter(
Jms.container(jmsConnectionFactory(), "containerSpecDestination")
.pubSubDomain(false)
.taskExecutor(Executors.newCachedThreadPool())
.get()))
.taskExecutor(Executors.newCachedThreadPool()))
.headerMapper(jmsHeaderMapper()))
.transform({ it: String -> it.trim({ it <= ' ' }) })
.channel(jmsOutboundInboundReplyChannel())
.get()
@Bean
fun jmsOutboundInboundReplyChannel() = MessageChannels.queue().get()
}

View File

@@ -171,6 +171,17 @@ If, on the other hand, you want to send the actual Spring Integration message to
NOTE: Regardless of the boolean value for payload extraction, the Spring Integration `MessageHeaders` map to JMS properties, as long as you rely on the default converter or provide a reference to another instance of `HeaderMappingMessageConverter`.
(The same holds true for 'inbound' adapters, except that, in those cases, the JMS properties map to Spring Integration `MessageHeaders`).
Starting with version 5.1, the `<int-jms:outbound-channel-adapter>` (`JmsSendingMessageHandler`) can be configured with the `deliveryModeExpression` and `timeToLiveExpression` properties to evaluate an appropriate QoS values for JMS message to send at runtime against request Spring `Message`.
The new `setMapInboundDeliveryMode(true)` and `setMapInboundExpiration(true)` options of the `DefaultJmsHeaderMapper` may facilitate as a source of the information for the dynamic `deliveryMode` and `timeToLive` from message headers:
====
[source,xml]
----
<int-jms:outbound-channel-adapter delivery-mode-expression="headers.jms_deliveryMode"
time-to-live-expression="headers.jms_expiration - T(System).currentTimeMillis()"/>
----
====
[[jms-ob-transactions]]
==== Transactions
@@ -585,6 +596,22 @@ On the inbound side, it is mapped as a `String`.
This is independent of the `jms_correlationId` header, which is mapped to and from the `JMSCorrelationID` header.
The `JMSCorrelationID` is generally used to correlate requests and replies, whereas the `correlationId` is often used to combine related messages into a group (such as with an aggregator or a resequencer).
Starting with version 5.1, the `DefaultJmsHeaderMapper` can be configured for mapping inbound `JMSDeliveryMode` and `JMSExpiration` properties:
====
[source,java]
----
@Bean
public DefaultJmsHeaderMapper jmsHeaderMapper() {
DefaultJmsHeaderMapper mapper = new DefaultJmsHeaderMapper();
mapper.setMapInboundDeliveryMode(true)
mapper.setMapInboundExpiration(true)
return mapper;
}
----
====
These JMS properties are mapped to the `JmsHeaders.DELIVERY_MODE` and `JmsHeaders.EXPIRATION` Spring Message headers respectively.
[[jms-conversion-and-marshalling]]
=== Message Conversion, Marshalling, and Unmarshalling
@@ -593,6 +620,7 @@ To do so, provide the bean name of an instance of `MessageConverter` that is ava
Also, to provide some consistency with marshaller and unmarshaller interfaces, Spring provides `MarshallingMessageConverter`, which you can configure with your own custom marshallers and unmarshallers.
The following example shows how to do so
====
[source,xml]
----
<int-jms:inbound-gateway request-destination="requestQueue"
@@ -609,6 +637,7 @@ The following example shows how to do so
</constructor-arg>
</bean>
----
====
NOTE: When you provide your own `MessageConverter` instance, it is still wrapped within the `HeaderMappingMessageConverter`.
This means that the 'extract-request-payload' and 'extract-reply-payload' properties can affect the actual objects passed to your converter.

View File

@@ -152,3 +152,11 @@ In addition the key and trust store types can now be configured on the `DefaultT
Since the Spring Social project has moved to https://spring.io/blog/2018/07/03/spring-social-end-of-life-announcement[end of life status], Twitter support in Spring Integration has been moved to the Extensions project.
See https://github.com/spring-projects/spring-integration-extensions/tree/master/spring-integration-social-twitter[Spring Integration Social Twitter] for more information.
[[x51.-jms]]
=== JMS Support
The `JmsSendingMessageHandler` now provides `deliveryModeExpression` and `timeToLiveExpression` options to determine respective QoS options for JMS message to send at runtime.
The `DefaultJmsHeaderMapper` now allows to map inbound `JMSDeliveryMode` and `JMSExpiration` properties via setting to `true` respective `setMapInboundDeliveryMode()` and `setMapInboundExpiration()` options.
See <<jms>> for more information.