INT-3288 JMS 'session-transacted' Attribute

JIRA: https://jira.springsource.org/browse/INT-3288

Previously, if you wanted to have a JMS outbound-channel-adapter
participate in an upstream JMS transaction (or use transactions
at all), you had to inject a `JmsTemplate` with `sessionTransacted`
set to `true`.

The `session-transacted` attribute has been added to the adapter,
allowing transactions to be used with the default `JmsTemplate`,
avoiding the need to inject a separate template.

Also, the inbound (polled) channel adapter incorrectly allowed
the `acknowledge` attribute to be set to `transacted` which is invalid and,
again, you had to inject an appropriately configured JmsTemplate
for transactions.

The inbound-channel-adapter now supports `session-transacted` as
well, and `acknowledge` can no longer be set to `transacted`.

There are no changes to the `message-driven-channel-adapter`, you
enable transactions with it by setting `acknowledge="transacted"`
as before.

INT-3288 Doc Polishing; Integration Tests
This commit is contained in:
Artem Bilan
2014-03-07 21:06:34 +02:00
parent 7b94f1e21e
commit 26d252bf00
16 changed files with 363 additions and 34 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@@ -16,6 +16,8 @@
package org.springframework.integration.jms.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
@@ -23,7 +25,6 @@ import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jms.DynamicJmsTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Utility methods and constants for JMS adapter parsers.
@@ -64,7 +65,7 @@ abstract class JmsAdapterParserUtils {
private static final String[] JMS_TEMPLATE_ATTRIBUTES = {
"connection-factory", "message-converter", "destination-resolver", "pub-sub-domain",
"time-to-live", "priority", "delivery-persistent", "explicit-qos-enabled", "acknowledge",
"receive-timeout"
"receive-timeout", "session-transacted"
};
@@ -138,8 +139,14 @@ abstract class JmsAdapterParserUtils {
}
Integer acknowledgeMode = parseAcknowledgeMode(element, parserContext);
if (acknowledgeMode != null) {
if (acknowledgeMode == SESSION_TRANSACTED) {
parserContext.getReaderContext().error(
"'transacted' is not a valid 'acknowledge-mode' here, use 'session-transacted'" +
" to enable transactions", element);
}
builder.addPropertyValue("sessionAcknowledgeMode", acknowledgeMode);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "session-transacted");
return builder.getBeanDefinition();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jms.JmsDestinationPollingSource;
import org.springframework.util.StringUtils;
/**
@@ -32,18 +33,19 @@ import org.springframework.util.StringUtils;
*/
public class JmsInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
@Override
protected boolean shouldGenerateId() {
return false;
}
@Override
protected boolean shouldGenerateIdAsFallback() {
return true;
}
@Override
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.jms.JmsDestinationPollingSource");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(JmsDestinationPollingSource.class);
String componentName = this.resolveId(element, builder.getBeanDefinition(), parserContext);
if (StringUtils.hasText(componentName)) {
builder.addPropertyValue("componentName", componentName);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2014 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.
@@ -24,19 +24,20 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jms.JmsSendingMessageHandler;
import org.springframework.util.StringUtils;
/**
* Parser for the <outbound-channel-adapter/> element of the jms namespace.
*
*
* @author Mark Fisher
* @author Gary Russell
*/
public class JmsOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
@Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.jms.JmsSendingMessageHandler");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(JmsSendingMessageHandler.class);
String jmsTemplate = element.getAttribute(JmsAdapterParserUtils.JMS_TEMPLATE_ATTRIBUTE);
String destination = element.getAttribute(JmsAdapterParserUtils.DESTINATION_ATTRIBUTE);
String destinationName = element.getAttribute(JmsAdapterParserUtils.DESTINATION_NAME_ATTRIBUTE);
@@ -53,7 +54,7 @@ public class JmsOutboundChannelAdapterParser extends AbstractOutboundChannelAdap
else {
builder.addConstructorArgValue(JmsAdapterParserUtils.parseJmsTemplateBeanDefinition(element, parserContext));
}
if (hasDestinationRef || hasDestinationName || hasDestinationExpression) {
if (!(hasDestinationRef ^ hasDestinationName ^ hasDestinationExpression)) {
parserContext.getReaderContext().error("The 'destination', 'destination-name', and " +

View File

@@ -548,6 +548,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="session-transacted" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Setting this attribute to true enables transactions. Cannot be specified if
a jms-template is specified. Default false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="jms-template" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
@@ -1178,6 +1186,16 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="session-transacted" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Setting this attribute to true enables transactions for the message send. If there is already
a JMS transaction in process, perhaps from a message-driven-channel-adapter upstream, the
same transaction is used; otherwise a new transaction is started. Cannot be specified if
a jms-template is specified. Default false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
@@ -1245,7 +1263,9 @@
<xsd:annotation>
<xsd:documentation><![CDATA[
The native JMS acknowledge mode: "auto", "client", "dups-ok" or "transacted".
The latter effectively activates a locally transacted Session.
The latter effectively activates a locally transacted Session. 'transacted'
is not allowed on the inbound-channel-adapter; use 'session-transacted' instead.
acknowlege="transacted" is used on the message-driven-channel-adapter.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -15,10 +15,13 @@
*/
package org.springframework.integration.jms;
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.transport.vm.VMTransport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.springframework.jms.connection.CachingConnectionFactory;
/**
@@ -31,8 +34,10 @@ import org.springframework.jms.connection.CachingConnectionFactory;
*/
public abstract class ActiveMQMultiContextTests {
private static final CachingConnectionFactory connectionFactory = new CachingConnectionFactory(
new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false"));
protected static final ConnectionFactory amqFactory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false");
protected static final CachingConnectionFactory connectionFactory = new CachingConnectionFactory(
amqFactory);
@BeforeClass
public static void startUp() throws Exception {
@@ -43,4 +48,5 @@ public abstract class ActiveMQMultiContextTests {
public static void shutDown() {
connectionFactory.resetConnection();
}
}

View File

@@ -0,0 +1,19 @@
<?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:int="http://www.springframework.org/schema/integration"
xmlns:int-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
http://www.springframework.org/schema/integration/jms http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd">
<int-jms:inbound-channel-adapter channel="out" session-transacted="true"
connection-factory="connectionFactory" destination-name="foo">
<int:poller fixed-delay="500"/>
</int-jms:inbound-channel-adapter>
<int:channel id="out">
<int:queue/>
</int:channel>
</beans>

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2014 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
*
* http://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;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import javax.jms.ConnectionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.integration.jms.JmsInboundChannelAdapterTests.CFConfig;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.messaging.PollableChannel;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Gary Russell
* @since 4.0
*
*/
@ContextConfiguration(classes=CFConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class JmsInboundChannelAdapterTests extends ActiveMQMultiContextTests {
@Autowired
private PollableChannel out;
@Test
public void testTransactionalReceive() {
JmsTemplate template = new JmsTemplate(connectionFactory);
template.convertAndSend("foo", "bar");
assertNotNull(out.receive(2000));
/*
* INT-3288 - previously acknowledge="transacted"
* Caused by: javax.jms.JMSException: acknowledgeMode SESSION_TRANSACTED cannot be used for an non-transacted Session
*/
assertNull(out.receive(1000));
}
@Configuration
@ImportResource("org/springframework/integration/jms/JmsInboundChannelAdapterTests-context.xml")
public static class CFConfig {
@Bean
public ConnectionFactory connectionFactory() {
return amqFactory;
}
}
}

View File

@@ -0,0 +1,26 @@
<?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:int="http://www.springframework.org/schema/integration"
xmlns:int-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
http://www.springframework.org/schema/integration/jms http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd">
<int-jms:message-driven-channel-adapter channel="toOut"
connection-factory="connectionFactory"
acknowledge="transacted"
destination-name="foo"/>
<int:publish-subscribe-channel id="toOut" />
<int-jms:outbound-channel-adapter channel="toOut" order="1"
connection-factory="connectionFactory"
destination-name="bar"
session-transacted="true" />
<int:service-activator input-channel="toOut" order="2" ref="aborter"/>
<bean id="aborter" class="org.springframework.integration.jms.JmsOutboundChannelAdapterTests$Aborter" />
</beans>

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2014 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
*
* http://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;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import javax.jms.ConnectionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.integration.jms.JmsOutboundChannelAdapterTests.CFConfig;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.messaging.PollableChannel;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Gary Russell
* @since 4.0
*
*/
@ContextConfiguration(classes=CFConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class JmsOutboundChannelAdapterTests extends ActiveMQMultiContextTests {
@Autowired
private PollableChannel out;
@Autowired
private Aborter aborter;
@Autowired
private JmsMessageDrivenEndpoint endpoint;
@Test
public void testTransactionalSend() {
JmsTemplate template = new JmsTemplate(connectionFactory);
template.convertAndSend("foo", "Hello, world!");
template.setReceiveTimeout(1000);
assertNotNull(template.receive("bar"));
this.aborter.abort = true;
template.convertAndSend("foo", "Hello, world!");
assertNull(template.receive("bar"));
endpoint.stop();
}
@Configuration
@ImportResource("org/springframework/integration/jms/JmsOutboundChannelAdapterTests-context.xml")
public static class CFConfig {
@Bean
public ConnectionFactory connectionFactory() {
return connectionFactory;
}
}
public static class Aborter {
private volatile boolean abort;
public void foo() {
if (abort) {
throw new RuntimeException("intentional");
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2014 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,7 +17,9 @@
package org.springframework.integration.jms.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.Properties;
@@ -27,21 +29,22 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.PollableChannel;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.PollableChannel;
/**
* @author Mark Fisher
* @author Gary Russell
*/
public class JmsInboundChannelAdapterParserTests {
long timeoutOnReceive = 3000;
@Test
public void adapterWithJmsTemplate() {
public void adapterWithJmsTemplate() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"jmsInboundWithJmsTemplate.xml", this.getClass());
PollableChannel output = (PollableChannel) context.getBean("output");
@@ -54,16 +57,18 @@ public class JmsInboundChannelAdapterParserTests {
assertNotNull("message should not be null", message);
assertEquals("polling-test", message.getPayload());
context.stop();
context.close();
}
@Test
public void adapterWithoutJmsTemplateAndAcknowlegeMode() {
public void adapterWithoutJmsTemplateAndAcknowlegeMode() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"jmsInboundWithJmsTemplate.xml", this.getClass());
JmsTemplate jmsTemplate =
JmsTemplate jmsTemplate =
TestUtils.getPropertyValue(context.getBean("inboundAdapterWithoutJmsTemplate"), "source.jmsTemplate", JmsTemplate.class);
assertEquals(0, jmsTemplate.getSessionAcknowledgeMode());
assertTrue(jmsTemplate.isSessionTransacted());
context.stop();
context.close();
}
@Test
@@ -74,7 +79,10 @@ public class JmsInboundChannelAdapterParserTests {
Message<?> message = output.receive(timeoutOnReceive);
assertNotNull("message should not be null", message);
assertEquals("polling-test", message.getPayload());
assertFalse(TestUtils.getPropertyValue(context.getBean("adapter"), "source.jmsTemplate", JmsTemplate.class)
.isSessionTransacted());
context.stop();
context.close();
}
@Test
@@ -86,11 +94,12 @@ public class JmsInboundChannelAdapterParserTests {
assertNotNull("message should not be null", message);
assertEquals("polling-test", message.getPayload());
context.stop();
context.close();
}
@Test(expected = BeanDefinitionStoreException.class)
public void adapterWithConnectionFactoryOnly() {
new ClassPathXmlApplicationContext("jmsInboundWithConnectionFactoryOnly.xml", this.getClass());
new ClassPathXmlApplicationContext("jmsInboundWithConnectionFactoryOnly.xml", this.getClass()).close();
}
@Test(expected = BeanCreationException.class)
@@ -114,11 +123,12 @@ public class JmsInboundChannelAdapterParserTests {
assertNotNull("message should not be null", message);
assertEquals("polling-test", message.getPayload());
context.stop();
context.close();
}
@Test(expected=BeanCreationException.class)
public void adapterWithDestinationNameOnly() {
new ClassPathXmlApplicationContext("jmsInboundWithDestinationNameOnly.xml", this.getClass());
new ClassPathXmlApplicationContext("jmsInboundWithDestinationNameOnly.xml", this.getClass()).close();
}
@Test
@@ -130,6 +140,7 @@ public class JmsInboundChannelAdapterParserTests {
assertNotNull("message should not be null", message);
assertEquals("polling-test", message.getPayload());
context.stop();
context.close();
}
@Test
@@ -143,6 +154,7 @@ public class JmsInboundChannelAdapterParserTests {
assertEquals("foo", message.getHeaders().get("testProperty"));
assertEquals(new Integer(123), message.getHeaders().get("testAttribute"));
context.stop();
context.close();
}
@Test
@@ -154,15 +166,17 @@ public class JmsInboundChannelAdapterParserTests {
assertNotNull("message should not be null", message);
assertEquals("test [with selector: TestProperty = 'foo']", message.getPayload());
context.stop();
context.close();
}
@Test
public void pollingAdapterWithReceiveTimeout() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"jmsInboundWithReceiveTimeout.xml", this.getClass());
JmsTemplate jmsTemplate =
JmsTemplate jmsTemplate =
TestUtils.getPropertyValue(context.getBean("adapter"), "source.jmsTemplate", JmsTemplate.class);
assertEquals(99, jmsTemplate.getReceiveTimeout());
context.close();
}
@Test
@@ -174,6 +188,7 @@ public class JmsInboundChannelAdapterParserTests {
assertNotNull("message should not be null", message);
assertEquals("converted-test", message.getPayload());
context.stop();
context.close();
}
@Test
@@ -185,6 +200,7 @@ public class JmsInboundChannelAdapterParserTests {
assertNotNull("message should not be null", message);
assertEquals("converted-test", message.getPayload());
context.stop();
context.close();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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,24 +17,26 @@
package org.springframework.integration.jms.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import javax.jms.DeliveryMode;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.jms.JmsHeaderMapper;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.GenericMessage;
/**
* @author Mark Fisher
@@ -52,6 +54,8 @@ public class JmsOutboundChannelAdapterParserTests {
DirectFieldAccessor accessor = new DirectFieldAccessor(
new DirectFieldAccessor(endpoint).getPropertyValue("handler"));
assertNotNull(accessor.getPropertyValue("jmsTemplate"));
assertTrue(TestUtils.getPropertyValue(endpoint, "handler.jmsTemplate.sessionTransacted", Boolean.class));
context.close();
}
@Test
@@ -62,6 +66,7 @@ public class JmsOutboundChannelAdapterParserTests {
MessageHandler handler = TestUtils.getPropertyValue(endpoint, "handler", MessageHandler.class);
handler.handleMessage(new GenericMessage<String>("foo"));
assertEquals(1, adviceCalled);
context.close();
}
@Test
@@ -72,6 +77,8 @@ public class JmsOutboundChannelAdapterParserTests {
DirectFieldAccessor accessor = new DirectFieldAccessor(
new DirectFieldAccessor(endpoint).getPropertyValue("handler"));
assertNotNull(accessor.getPropertyValue("jmsTemplate"));
assertFalse(TestUtils.getPropertyValue(endpoint, "handler.jmsTemplate.sessionTransacted", Boolean.class));
context.close();
}
@Test
@@ -82,6 +89,7 @@ public class JmsOutboundChannelAdapterParserTests {
DirectFieldAccessor accessor = new DirectFieldAccessor(
new DirectFieldAccessor(endpoint).getPropertyValue("handler"));
assertNotNull(accessor.getPropertyValue("jmsTemplate"));
context.close();
}
@Test
@@ -93,6 +101,7 @@ public class JmsOutboundChannelAdapterParserTests {
new DirectFieldAccessor(endpoint).getPropertyValue("handler"));
Object order = accessor.getPropertyValue("order");
assertEquals(123, order);
context.close();
}
@Test
@@ -105,6 +114,7 @@ public class JmsOutboundChannelAdapterParserTests {
JmsHeaderMapper headerMapper = (JmsHeaderMapper) accessor.getPropertyValue("headerMapper");
assertNotNull(headerMapper);
assertEquals(TestJmsHeaderMapper.class, headerMapper.getClass());
context.close();
}
@Test
@@ -116,6 +126,7 @@ public class JmsOutboundChannelAdapterParserTests {
JmsTemplate jmsTemplate = (JmsTemplate) handlerAccessor.getPropertyValue("jmsTemplate");
assertNotNull(jmsTemplate);
assertEquals(context.getBean("template"), jmsTemplate);
context.close();
}
@Test
@@ -130,6 +141,7 @@ public class JmsOutboundChannelAdapterParserTests {
assertTrue(jmsTemplate.isExplicitQosEnabled());
assertEquals(7, jmsTemplate.getPriority());
assertEquals(12345, jmsTemplate.getTimeToLive());
context.close();
}
@Test
@@ -143,6 +155,7 @@ public class JmsOutboundChannelAdapterParserTests {
MessageConverter messageConverter = jmsTemlate.getMessageConverter();
assertNotNull(messageConverter);
assertEquals(TestMessageConverter.class, messageConverter.getClass());
context.close();
}
@Test(expected = BeanDefinitionStoreException.class)
@@ -168,6 +181,7 @@ public class JmsOutboundChannelAdapterParserTests {
assertEquals(12345L, accessor.getPropertyValue("timeToLive"));
assertEquals(7, accessor.getPropertyValue("priority"));
assertEquals(DeliveryMode.NON_PERSISTENT, accessor.getPropertyValue("deliveryMode"));
context.close();
}
@Test
@@ -179,6 +193,7 @@ public class JmsOutboundChannelAdapterParserTests {
new DirectFieldAccessor(new DirectFieldAccessor(endpoint).getPropertyValue("handler"))
.getPropertyValue("jmsTemplate"));
assertEquals(false, accessor.getPropertyValue("explicitQosEnabled"));
context.close();
}
public static class FooAdvice extends AbstractRequestHandlerAdvice {

View File

@@ -10,7 +10,8 @@
http://www.springframework.org/schema/integration/jms
http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd">
<jms:inbound-channel-adapter channel="output"
<jms:inbound-channel-adapter id="adapter"
channel="output"
connection-factory="testConnectionFactory"
destination="testDestination"/>

View File

@@ -14,7 +14,8 @@
<jms:inbound-channel-adapter id="inboundAdapter" jms-template="jmsTemplate" channel="output"/>
<jms:inbound-channel-adapter id="inboundAdapterWithoutJmsTemplate" channel="outputA" acknowledge="transacted"
<jms:inbound-channel-adapter id="inboundAdapterWithoutJmsTemplate" channel="outputA"
session-transacted="true"
destination-name="foo.bar"/>
<integration:channel id="outputA"/>

View File

@@ -15,7 +15,8 @@
<jms:outbound-channel-adapter id="adapter"
channel="input"
connection-factory="testConnectionFactory"
destination="testDestination"/>
destination="testDestination"
session-transacted="true" />
<bean id="testConnectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<constructor-arg>

View File

@@ -75,6 +75,28 @@
<int:poller fixed-rate="30000"/>
</int-jms:inbound-channel-adapter>]]></programlisting>
</para>
<section id="jms-ib-transactions">
<title>Transactions</title>
<para>
Starting with <emphasis>version 4.0</emphasis>, the inbound channel adapter supports the
<code>session-transacted</code> attribute. In earlier versions, you had to inject a
<classname>JmsTemplate</classname> with <code>sessionTransacted</code> set to <code>true</code>.
(The adapter did allow the <code>acknowledge</code> attribute to be set to
<code>transacted</code> but this was incorrect and did not work).
</para>
<para>
Note, however, that setting <code>session-transacted</code> to <code>true</code> has
little value because the transaction is committed immediately after the <code>receive()</code>
and before the message is sent to the <code>channel</code>,
</para>
<para>
If you want the entire flow to be transactional (for example if there is a downstream
outbound channel adapter), you must use a <code>transactional</code> poller, with a
<classname>JmsTransactionManager</classname>. Or, consider using a
<code>jms-message-driven-channel-adapter</code> with <code>acknowledge</code>
set to <code>transacted</code>.
</para>
</section>
</section>
<section id="jms-message-driven-channel-adapter">
@@ -151,6 +173,18 @@
those cases, it's the JMS properties mapping <emphasis>to</emphasis> Spring Integration MessageHeaders).
</note>
</para>
<section id="jms-ob-transactions">
<title>Transactions</title>
<para>
Starting with <emphasis>version 4.0</emphasis>, the outbound channel adapter supports the
<code>session-transacted</code> attribute. In earlier versions, you had to inject a
<classname>JmsTemplate</classname> with <code>sessionTransacted</code> set to <code>true</code>.
The attribute now sets the property on the built-in default <classname>JmsTemplate</classname>.
If a transaction exists (perhaps from an upstream <code>message-driven-channel-adapter</code>)
the send will be performed within the same transaction. Otherwise a new transaction will
be started.
</para>
</section>
</section>
<section id="jms-inbound-gateway">

View File

@@ -107,6 +107,24 @@
considered for outbound messages. For more information see <xref linkend="jms-header-mapping"/>.
</para>
</section>
<section id="4.0-jms-ob">
<title>JMS Outbound Channel Adapter</title>
<para>
The JMS outbound channel adapter now supports the <code>session-transacted</code> attribute
(default false). Previously, you had to inject a customized <classname>JmsTemplate</classname>
to use transactions. See <xref linkend="jms-outbound-channel-adapter"/>.
</para>
</section>
<section id="4.0-jms-ib">
<title>JMS Inbound Channel Adapter</title>
<para>
The JMS inbound channel adapter now supports the <code>session-transacted</code> attribute
(default false). Previously, you had to inject a customized <classname>JmsTemplate</classname>
to use transactions (the adapter allowed 'transacted' in the acknowledgeMode which was
incorrect, and didn't work; this value is no longer allowed). See
<xref linkend="jms-inbound-channel-adapter"/>.
</para>
</section>
<section id="4.0-datatype-channel">
<title>Datatype Channels</title>
<para>