INT-3770: Add TX Support from Mid-flow

JIRA: https://jira.spring.io/browse/INT-3770,
https://jira.spring.io/browse/INT-4107

Having `TransactionHandleMessageAdvice` we can start TX from any `MessageHandler.handleMessage()`

* Add `<transactional>` alongside with the `<request-handler-advice-chain>` for those components which produce reply
* Merge `<transactional>` and `<request-handler-advice-chain>` configuration to a single `ManagedList`
* Rework JPA `<transactional>` in favor of common solution
* Some polishing and refactoring

AbstractPollingEndpoint: avoid `new ArrayList` if we don't  have `receiveOnlyAdvice`s
This commit is contained in:
Artem Bilan
2016-11-02 19:32:16 -04:00
committed by Gary Russell
parent cfceca8518
commit 5cca8e8e01
37 changed files with 327 additions and 170 deletions

View File

@@ -29,6 +29,7 @@ import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
@@ -86,11 +87,18 @@ public abstract class AbstractConsumerEndpointParser extends AbstractBeanDefinit
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(handlerBuilder, element, "output-channel");
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "order");
Element txElement = DomUtils.getChildElementByTagName(element, "transactional");
Element adviceChainElement = DomUtils.getChildElementByTagName(element,
IntegrationNamespaceUtils.REQUEST_HANDLER_ADVICE_CHAIN);
IntegrationNamespaceUtils.configureAndSetAdviceChainIfPresent(adviceChainElement, null,
@SuppressWarnings("rawtypes")
ManagedList adviceChain = IntegrationNamespaceUtils.configureAdviceChain(adviceChainElement, txElement, true,
handlerBuilder.getRawBeanDefinition(), parserContext);
if (!CollectionUtils.isEmpty(adviceChain)) {
handlerBuilder.addPropertyValue("adviceChain", adviceChain);
}
AbstractBeanDefinition handlerBeanDefinition = handlerBuilder.getBeanDefinition();
String inputChannelAttributeName = this.getInputChannelAttributeName();
boolean hasInputChannelAttribute = element.hasAttribute(inputChannelAttributeName);
@@ -121,6 +129,10 @@ public abstract class AbstractConsumerEndpointParser extends AbstractBeanDefinit
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class);
if (!CollectionUtils.isEmpty(adviceChain)) {
builder.addPropertyValue("adviceChain", adviceChain);
}
String handlerBeanName = BeanDefinitionReaderUtils.generateBeanName(handlerBeanDefinition, parserContext.getRegistry());
String[] handlerAlias = IntegrationNamespaceUtils.generateAlias(element);
parserContext.registerBeanComponent(new BeanComponentDefinition(handlerBeanDefinition, handlerBeanName, handlerAlias));

View File

@@ -27,6 +27,7 @@ import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.ConsumerEndpointFactoryBean;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
@@ -81,12 +82,14 @@ public abstract class AbstractOutboundChannelAdapterParser extends AbstractChann
private void configureRequestHandlerAdviceChain(Element element, ParserContext parserContext,
BeanDefinition handlerBeanDefinition, BeanDefinitionBuilder consumerBuilder) {
Element txElement = DomUtils.getChildElementByTagName(element, "transactional");
Element adviceChainElement = DomUtils.getChildElementByTagName(element,
IntegrationNamespaceUtils.REQUEST_HANDLER_ADVICE_CHAIN);
@SuppressWarnings("rawtypes")
ManagedList adviceChain =
IntegrationNamespaceUtils.configureAdviceChain(adviceChainElement, null, handlerBeanDefinition, parserContext);
if (adviceChain != null) {
IntegrationNamespaceUtils.configureAdviceChain(adviceChainElement, txElement, handlerBeanDefinition,
parserContext);
if (!CollectionUtils.isEmpty(adviceChain)) {
/*
* For ARPMH, the advice chain is injected so just the handleRequestMessage method is advised.
* Sometime ARPMHs do double duty as a gateway and a channel adapter. The parser subclass

View File

@@ -101,6 +101,10 @@ public class DelayerParser extends AbstractConsumerEndpointParser {
IntegrationNamespaceUtils.configureAndSetAdviceChainIfPresent(adviceChainElement, txElement,
builder.getRawBeanDefinition(), parserContext, "delayedAdviceChain");
if (txElement != null) {
element.removeChild(txElement);
}
return builder;
}

View File

@@ -48,10 +48,12 @@ import org.springframework.integration.config.FixedSubscriberChannelBeanFactoryP
import org.springframework.integration.config.IntegrationConfigUtils;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.endpoint.AbstractPollingEndpoint;
import org.springframework.integration.transaction.TransactionHandleMessageAdvice;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
@@ -381,19 +383,34 @@ public abstract class IntegrationNamespaceUtils {
* Parse a "transactional" element and configure a {@link TransactionInterceptor}
* with "transactionManager" and other "transactionDefinition" properties.
* For example, this advisor will be applied on the Polling Task proxy.
*
* @param txElement The transactional element.
* @return The bean definition.
*
* @see AbstractPollingEndpoint
*/
public static BeanDefinition configureTransactionAttributes(Element txElement) {
return configureTransactionAttributes(txElement, false);
}
/**
* Parse a "transactional" element and configure a {@link TransactionInterceptor}
* or {@link TransactionHandleMessageAdvice}
* with "transactionManager" and other "transactionDefinition" properties.
* For example, this advisor will be applied on the Polling Task proxy.
* @param txElement The transactional element.
* @param handleMessageAdvice flag if to use {@link TransactionHandleMessageAdvice}
* or regular {@link TransactionInterceptor}
* @return The bean definition.
* @see AbstractPollingEndpoint
*/
public static BeanDefinition configureTransactionAttributes(Element txElement, boolean handleMessageAdvice) {
BeanDefinition txDefinition = configureTransactionDefinition(txElement);
BeanDefinitionBuilder attributeSourceBuilder =
BeanDefinitionBuilder.genericBeanDefinition(MatchAlwaysTransactionAttributeSource.class);
attributeSourceBuilder.addPropertyValue("transactionAttribute", txDefinition);
BeanDefinitionBuilder txInterceptorBuilder =
BeanDefinitionBuilder.genericBeanDefinition(TransactionInterceptor.class);
BeanDefinitionBuilder.genericBeanDefinition(handleMessageAdvice
? TransactionHandleMessageAdvice.class
: TransactionInterceptor.class);
txInterceptorBuilder.addPropertyReference("transactionManager", txElement.getAttribute("transaction-manager"));
txInterceptorBuilder.addPropertyValue("transactionAttributeSource", attributeSourceBuilder.getBeanDefinition());
return txInterceptorBuilder.getBeanDefinition();
@@ -426,31 +443,47 @@ public abstract class IntegrationNamespaceUtils {
public static void configureAndSetAdviceChainIfPresent(Element adviceChainElement, Element txElement,
BeanDefinition parentBeanDefinition, ParserContext parserContext) {
configureAndSetAdviceChainIfPresent(adviceChainElement, txElement, parentBeanDefinition, parserContext,
"adviceChain");
configureAndSetAdviceChainIfPresent(adviceChainElement, txElement, false, parentBeanDefinition, parserContext);
}
public static void configureAndSetAdviceChainIfPresent(Element adviceChainElement,
Element txElement, boolean handleMessageAdvice, BeanDefinition parentBeanDefinition,
ParserContext parserContext) {
configureAndSetAdviceChainIfPresent(adviceChainElement, txElement, handleMessageAdvice,
parentBeanDefinition, parserContext, "adviceChain");
}
public static void configureAndSetAdviceChainIfPresent(Element adviceChainElement, Element txElement,
BeanDefinition parentBeanDefinition, ParserContext parserContext, String propertyName) {
configureAndSetAdviceChainIfPresent(adviceChainElement, txElement, false, parentBeanDefinition,
parserContext, propertyName);
}
@SuppressWarnings({ "rawtypes" })
public static void configureAndSetAdviceChainIfPresent(Element adviceChainElement, Element txElement,
BeanDefinition parentBeanDefinition, ParserContext parserContext, String propertyName) {
ManagedList adviceChain = configureAdviceChain(adviceChainElement, txElement, parentBeanDefinition,
parserContext);
if (adviceChain != null) {
boolean handleMessageAdvice, BeanDefinition parentBeanDefinition, ParserContext parserContext,
String propertyName) {
ManagedList adviceChain = configureAdviceChain(adviceChainElement, txElement, handleMessageAdvice,
parentBeanDefinition, parserContext);
if (!CollectionUtils.isEmpty(adviceChain)) {
parentBeanDefinition.getPropertyValues().add(propertyName, adviceChain);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@SuppressWarnings("rawtypes")
public static ManagedList configureAdviceChain(Element adviceChainElement, Element txElement,
BeanDefinition parentBeanDefinition, ParserContext parserContext) {
ManagedList adviceChain = null;
// Schema validation ensures txElement and adviceChainElement are mutually exclusive
return configureAdviceChain(adviceChainElement, txElement, false, parentBeanDefinition, parserContext);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static ManagedList configureAdviceChain(Element adviceChainElement, Element txElement,
boolean handleMessageAdvice, BeanDefinition parentBeanDefinition, ParserContext parserContext) {
ManagedList adviceChain = new ManagedList();
if (txElement != null) {
adviceChain = new ManagedList();
adviceChain.add(IntegrationNamespaceUtils.configureTransactionAttributes(txElement));
adviceChain.add(configureTransactionAttributes(txElement, handleMessageAdvice));
}
if (adviceChainElement != null) {
adviceChain = new ManagedList();
NodeList childNodes = adviceChainElement.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);

View File

@@ -16,12 +16,12 @@
package org.springframework.integration.endpoint;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledFuture;
import java.util.stream.Collectors;
import org.aopalliance.aop.Advice;
@@ -174,30 +174,26 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
@SuppressWarnings("unchecked")
private Runnable createPoller() throws Exception {
List<Advice> receiveOnlyAdviceChain = new ArrayList<Advice>();
List<Advice> receiveOnlyAdviceChain = null;
if (!CollectionUtils.isEmpty(this.adviceChain)) {
for (Advice advice : this.adviceChain) {
if (isReceiveOnlyAdvice(advice)) {
receiveOnlyAdviceChain.add(advice);
}
}
receiveOnlyAdviceChain = this.adviceChain.stream()
.filter(this::isReceiveOnlyAdvice)
.collect(Collectors.toList());
}
Callable<Boolean> pollingTask = () -> doPoll();
Callable<Boolean> pollingTask = this::doPoll;
List<Advice> adviceChain = this.adviceChain;
if (!CollectionUtils.isEmpty(adviceChain)) {
ProxyFactory proxyFactory = new ProxyFactory(pollingTask);
if (!CollectionUtils.isEmpty(adviceChain)) {
for (Advice advice : adviceChain) {
if (!isReceiveOnlyAdvice(advice)) {
proxyFactory.addAdvice(advice);
}
}
adviceChain.stream()
.filter(advice -> !isReceiveOnlyAdvice(advice))
.forEach(proxyFactory::addAdvice);
}
pollingTask = (Callable<Boolean>) proxyFactory.getProxy(this.beanClassLoader);
}
if (receiveOnlyAdviceChain.size() > 0) {
if (receiveOnlyAdviceChain != null) {
applyReceiveOnlyAdviceChain(receiveOnlyAdviceChain);
}
return new Poller(pollingTask);

View File

@@ -132,9 +132,10 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint
@Override
protected void applyReceiveOnlyAdviceChain(Collection<Advice> chain) {
if (AopUtils.isAopProxy(this.source)) {
this.appliedAdvices.forEach(((Advised) this.source)::removeAdvice);
Advised source = (Advised) this.source;
this.appliedAdvices.forEach(source::removeAdvice);
for (Advice advice : chain) {
((Advised) this.source).addAdvisor(adviceToReceiveAdvisor(advice));
source.addAdvisor(adviceToReceiveAdvisor(advice));
}
}
else {

View File

@@ -1367,6 +1367,7 @@
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="transactional" type="transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="handlerAdviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="request-channel" type="xsd:string" use="optional">
@@ -1665,6 +1666,7 @@
</xsd:annotation>
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element name="transactional" type="transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="handlerAdviceChainType" minOccurs="0" maxOccurs="1" />
<xsd:element ref="poller" />
</xsd:choice>
@@ -2871,6 +2873,7 @@
<xsd:complexContent>
<xsd:extension base="expressionOrInnerEndpointDefinitionAwareNoAdviceChain">
<xsd:sequence>
<xsd:element name="transactional" type="transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:complexContent>
@@ -4125,6 +4128,7 @@
<xsd:choice minOccurs="0" maxOccurs="3">
<xsd:element name="poller" type="basePollerType" minOccurs="0" maxOccurs="1" />
<xsd:element name="expression" type="innerExpressionType" minOccurs="0" maxOccurs="1" />
<xsd:element name="transactional" type="transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="handlerAdviceChainType" minOccurs="0" maxOccurs="1" />
<xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="1" />
</xsd:choice>

View File

@@ -27,6 +27,9 @@
input-channel="aggregatorWithCustomMGPReferenceInput" output-channel="outputChannel"/>
<channel id="completelyDefinedAggregatorInput"/>
<beans:bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager"/>
<aggregator id="completelyDefinedAggregator"
input-channel="completelyDefinedAggregatorInput"
output-channel="outputChannel"
@@ -44,7 +47,7 @@
scheduler="scheduler"
message-store="store"
order="5">
<expire-advice-chain/>
<expire-transactional/>
</aggregator>
<beans:bean id="lockRegistry" class="org.springframework.integration.support.locks.DefaultLockRegistry"/>
@@ -116,4 +119,5 @@
class="org.springframework.integration.config.MaxValueReleaseStrategy">
<beans:constructor-arg value="10" />
</beans:bean>
</beans:beans>

View File

@@ -34,11 +34,12 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -63,6 +64,7 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Marius Bogoevici
@@ -73,15 +75,12 @@ import org.springframework.messaging.SubscribableChannel;
* @author Gunnar Hillert
* @author Gary Russell
*/
@RunWith(SpringRunner.class)
public class AggregatorParserTests {
@Autowired
private ApplicationContext context;
@Before
public void setUp() {
this.context = new ClassPathXmlApplicationContext("aggregatorParserTests.xml", this.getClass());
}
@Test
public void testAggregation() {
MessageChannel input = (MessageChannel) context.getBean("aggregatorWithReferenceInput");
@@ -90,11 +89,11 @@ public class AggregatorParserTests {
outboundMessages.add(createMessage("123", "id1", 3, 1, null));
outboundMessages.add(createMessage("789", "id1", 3, 3, null));
outboundMessages.add(createMessage("456", "id1", 3, 2, null));
for (Message<?> message : outboundMessages) {
input.send(message);
}
assertEquals("One and only one message must have been aggregated", 1, aggregatorBean.getAggregatedMessages()
.size());
outboundMessages.forEach(input::send);
assertEquals("One and only one message must have been aggregated", 1,
aggregatorBean.getAggregatedMessages().size());
Message<?> aggregatedMessage = aggregatorBean.getAggregatedMessages().get("id1");
assertEquals("The aggregated message payload is not correct", "123456789", aggregatedMessage.getPayload());
Object mbf = context.getBean(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME);
@@ -111,9 +110,9 @@ public class AggregatorParserTests {
outboundMessages.add(createMessage("123", "id1", 3, 1, null));
outboundMessages.add(createMessage("789", "id1", 3, 3, null));
outboundMessages.add(createMessage("456", "id1", 3, 2, null));
for (Message<?> message : outboundMessages) {
input.send(message);
}
outboundMessages.forEach(input::send);
assertEquals(3, output.getQueueSize());
output.purge(null);
}
@@ -127,7 +126,9 @@ public class AggregatorParserTests {
outboundMessages.add(createMessage("123", "id1", 3, 1, null));
outboundMessages.add(createMessage("789", "id1", 3, 3, null));
outboundMessages.add(createMessage("456", "id1", 3, 2, null));
outboundMessages.forEach(input::send);
assertEquals(3, output.getQueueSize());
output.purge(null);
}
@@ -142,7 +143,9 @@ public class AggregatorParserTests {
outboundMessages.add(MessageBuilder.withPayload("123").setHeader("foo", "1").build());
outboundMessages.add(MessageBuilder.withPayload("456").setHeader("foo", "1").build());
outboundMessages.add(MessageBuilder.withPayload("789").setHeader("foo", "1").build());
outboundMessages.forEach(input::send);
assertEquals("The aggregated message payload is not correct", "[123]", aggregatedMessage.get().getPayload()
.toString());
Object mbf = context.getBean(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME);

View File

@@ -407,6 +407,7 @@ Only files matching this regular expression will be picked up by this adapter.
<xsd:complexType name="outboundFileBaseType">
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string">
@@ -651,6 +652,7 @@ Only files matching this regular expression will be picked up by this adapter.
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element name="poller" type="integration:basePollerType" minOccurs="0" maxOccurs="1" />
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attributeGroup ref="integration:inputOutputChannelGroup" />

View File

@@ -206,6 +206,7 @@
<xsd:extension base="base-outbound-adapter-type">
<xsd:all>
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
minOccurs="0" maxOccurs="1" />
</xsd:all>

View File

@@ -79,6 +79,7 @@
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0"
maxOccurs="1" />
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
minOccurs="0" maxOccurs="1" />
</xsd:choice>

View File

@@ -419,6 +419,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType" minOccurs="0" maxOccurs="1" />
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:choice>

View File

@@ -374,6 +374,7 @@
<xsd:complexType>
<xsd:all>
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
minOccurs="0" maxOccurs="1" />
</xsd:all>

View File

@@ -774,6 +774,7 @@
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="3">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
minOccurs="0" maxOccurs="1" />
<xsd:element name="reply-listener" minOccurs="0" maxOccurs="1">

View File

@@ -80,6 +80,7 @@
<xsd:extension base="operationInvokingType">
<xsd:all>
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
minOccurs="0" maxOccurs="1" />
</xsd:all>

View File

@@ -24,10 +24,12 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.spy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.aopalliance.aop.Advice;
@@ -44,6 +46,7 @@ import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.config.GlobalChannelInterceptor;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.handler.ServiceActivatingHandler;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
@@ -55,6 +58,8 @@ import org.springframework.integration.metadata.SimpleMetadataStore;
import org.springframework.integration.selector.MetadataStoreSelector;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.transaction.PseudoTransactionManager;
import org.springframework.integration.transaction.TransactionInterceptorBuilder;
import org.springframework.integration.transformer.Transformer;
import org.springframework.jmx.support.MBeanServerFactoryBean;
import org.springframework.messaging.Message;
@@ -62,11 +67,15 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
@@ -77,8 +86,7 @@ import com.hazelcast.core.HazelcastInstance;
* @author Gary Russell
* @since 4.1
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@DirtiesContext
public class IdempotentReceiverIntegrationTests {
@@ -109,11 +117,14 @@ public class IdempotentReceiverIntegrationTests {
@Autowired
private MessageChannel annotatedBeanMessageHandlerChannel2;
@Autowired
private AtomicBoolean txSupplied;
@Test
public void testIdempotentReceiver() {
this.idempotentReceiverInterceptor.setThrowExceptionOnRejection(true);
TestUtils.getPropertyValue(this.store, "metadata", Map.class).clear();
Message<String> message = new GenericMessage<String>("foo");
Message<String> message = new GenericMessage<>("foo");
this.input.send(message);
Message<?> receive = this.output.receive(10000);
assertNotNull(receive);
@@ -136,18 +147,22 @@ public class IdempotentReceiverIntegrationTests {
assertEquals(2, this.adviceCalled.get());
assertTrue(receive.getHeaders().get(IntegrationMessageHeaderAccessor.DUPLICATE_MESSAGE, Boolean.class));
assertEquals(1, TestUtils.getPropertyValue(store, "metadata", Map.class).size());
assertTrue(this.txSupplied.get());
}
@Test
public void testIdempotentReceiverOnMethod() {
TestUtils.getPropertyValue(this.store, "metadata", Map.class).clear();
Message<String> message = new GenericMessage<String>("foo");
Message<String> message = new GenericMessage<>("foo");
this.annotatedMethodChannel.send(message);
this.annotatedMethodChannel.send(message);
assertEquals(2, this.fooService.messages.size());
assertTrue(this.fooService.messages.get(1).getHeaders().get(IntegrationMessageHeaderAccessor.DUPLICATE_MESSAGE,
Boolean.class));
assertTrue(
this.fooService.messages.get(1)
.getHeaders()
.get(IntegrationMessageHeaderAccessor.DUPLICATE_MESSAGE, Boolean.class));
}
@Test
@@ -197,7 +212,9 @@ public class IdempotentReceiverIntegrationTests {
@Bean
public ConcurrentMetadataStore store() {
return new SimpleMetadataStore(hazelcastInstance().<String, String>getMap("idempotentReceiverMetadataStore"));
return new SimpleMetadataStore(
hazelcastInstance()
.getMap("idempotentReceiverMetadataStore"));
}
@Bean
@@ -208,6 +225,17 @@ public class IdempotentReceiverIntegrationTests {
message -> message.getPayload().toString().toUpperCase(), store()));
}
@Bean
public PlatformTransactionManager transactionManager() {
return spy(new PseudoTransactionManager());
}
@Bean
public TransactionInterceptor transactionInterceptor() {
return new TransactionInterceptorBuilder(true)
.build();
}
@Bean
public MessageChannel input() {
return new DirectChannel();
@@ -218,9 +246,31 @@ public class IdempotentReceiverIntegrationTests {
return new QueueChannel();
}
@Bean
public AtomicBoolean txSupplied() {
return new AtomicBoolean();
}
@Bean
@GlobalChannelInterceptor(patterns = "output")
public ChannelInterceptor txSuppliedChannelInterceptor(final AtomicBoolean txSupplied) {
return new ChannelInterceptorAdapter() {
@Override
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
super.postSend(message, channel, sent);
txSupplied.set(TransactionSynchronizationManager.isActualTransactionActive());
}
};
}
@Bean
@org.springframework.integration.annotation.Transformer(inputChannel = "input",
outputChannel = "output", adviceChain = {"fooAdvice", "idempotentReceiverInterceptor"})
outputChannel = "output",
adviceChain = { "fooAdvice",
"idempotentReceiverInterceptor",
"transactionInterceptor" })
public Transformer transformer() {
return message -> message;
}
@@ -258,14 +308,7 @@ public class IdempotentReceiverIntegrationTests {
@ServiceActivator(inputChannel = "annotatedBeanMessageHandlerChannel")
@IdempotentReceiver("idempotentReceiverInterceptor")
public MessageHandler messageHandler() {
return new ServiceActivatingHandler(new MessageProcessor<Object>() {
@Override
public Object processMessage(Message<?> message) {
return message;
}
});
return new ServiceActivatingHandler((MessageProcessor<Object>) message -> message);
}
@Bean

View File

@@ -6,6 +6,6 @@ log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m
log4j.category.org.springframework=WARN
#log4j.category.org.springframework.integration=DEBUG
#log4j.category.org.springframework.beans.factory=DEBUG
#log4j.category.org.springframework.integration.monitor=TRACE
log4j.category.org.springframework.integration=WARN
log4j.category.org.springframework.integration.jmx=WARN
log4j.category.org.springframework.integration.monitor=WARN

View File

@@ -18,15 +18,12 @@ package org.springframework.integration.jpa.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jpa.outbound.JpaOutboundGatewayFactoryBean;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* The Abstract Parser for the JPA Outbound Gateways.
@@ -56,16 +53,6 @@ public abstract class AbstractJpaOutboundGatewayParser extends AbstractConsumerE
jpaOutboundGatewayBuilder.addPropertyReference("outputChannel", replyChannel);
}
final Element transactionalElement = DomUtils.getChildElementByTagName(gatewayElement, "transactional");
if (transactionalElement != null) {
BeanDefinition txAdviceDefinition =
IntegrationNamespaceUtils.configureTransactionAttributes(transactionalElement);
ManagedList<BeanDefinition> adviceChain = new ManagedList<BeanDefinition>();
adviceChain.add(txAdviceDefinition);
jpaOutboundGatewayBuilder.addPropertyValue("txAdviceChain", adviceChain);
}
return jpaOutboundGatewayBuilder;
}

View File

@@ -22,12 +22,10 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jpa.outbound.JpaOutboundGatewayFactoryBean;
import org.springframework.util.xml.DomUtils;
/**
* The parser for JPA outbound channel adapter
@@ -77,16 +75,6 @@ public class JpaOutboundChannelAdapterParser extends AbstractOutboundChannelAdap
jpaOutboundChannelAdapterBuilder.addPropertyReference("jpaExecutor", jpaExecutorBeanName)
.addPropertyValue("producesReply", Boolean.FALSE);
final Element transactionalElement = DomUtils.getChildElementByTagName(element, "transactional");
if (transactionalElement != null) {
BeanDefinition txAdviceDefinition =
IntegrationNamespaceUtils.configureTransactionAttributes(transactionalElement);
ManagedList<BeanDefinition> adviceChain = new ManagedList<BeanDefinition>();
adviceChain.add(txAdviceDefinition);
jpaOutboundChannelAdapterBuilder.addPropertyValue("txAdviceChain", adviceChain);
}
return jpaOutboundChannelAdapterBuilder.getBeanDefinition();
}

View File

@@ -20,7 +20,6 @@ import java.util.List;
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.integration.jpa.core.JpaExecutor;
@@ -28,8 +27,6 @@ import org.springframework.integration.jpa.support.OutboundGatewayType;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
/**
* The {@link JpaOutboundGatewayFactoryBean} creates instances of the
@@ -50,18 +47,11 @@ public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean<MessageHa
private OutboundGatewayType gatewayType = OutboundGatewayType.UPDATING;
/**
* &lt;transactional /&gt; element applies to entire flow from this point
*/
private List<Advice> txAdviceChain;
/**
* &lt;request-handler-advice-chain /&gt; only applies to the handleRequestMessage.
*/
private List<Advice> adviceChain;
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private boolean producesReply = true;
private MessageChannel outputChannel;
@@ -85,10 +75,6 @@ public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean<MessageHa
this.gatewayType = gatewayType;
}
public void setTxAdviceChain(List<Advice> txAdviceChain) {
this.txAdviceChain = txAdviceChain;
}
public void setAdviceChain(List<Advice> adviceChain) {
this.adviceChain = adviceChain;
}
@@ -128,12 +114,6 @@ public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean<MessageHa
this.componentName = componentName;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
super.setBeanClassLoader(classLoader);
this.beanClassLoader = classLoader;
}
@Override
public Class<?> getObjectType() {
return MessageHandler.class;
@@ -154,18 +134,6 @@ public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean<MessageHa
}
jpaOutboundGateway.setBeanFactory(this.getBeanFactory());
jpaOutboundGateway.afterPropertiesSet();
if (!CollectionUtils.isEmpty(this.txAdviceChain)) {
ProxyFactory proxyFactory = new ProxyFactory(jpaOutboundGateway);
if (!CollectionUtils.isEmpty(this.txAdviceChain)) {
for (Advice advice : this.txAdviceChain) {
proxyFactory.addAdvice(advice);
}
}
return (MessageHandler) proxyFactory.getProxy(this.beanClassLoader);
}
return jpaOutboundGateway;
}

View File

@@ -21,24 +21,26 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Proxy;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.jpa.core.JpaExecutor;
import org.springframework.integration.jpa.core.JpaOperations;
import org.springframework.integration.jpa.support.JpaParameter;
import org.springframework.integration.jpa.support.PersistMode;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.GenericMessage;
/**
*
@@ -166,7 +168,7 @@ public class JpaMessageHandlerParserTests {
@SuppressWarnings("unchecked")
@Test
public void testProcedurepParametersAreSet() throws Exception {
public void testProcedureParametersAreSet() throws Exception {
setUp("JpaMessageHandlerParserTestsWithEmFactory.xml", getClass());
final JpaExecutor jpaExecutor = TestUtils.getPropertyValue(this.consumer, "handler.jpaExecutor", JpaExecutor.class);
@@ -197,9 +199,11 @@ public class JpaMessageHandlerParserTests {
setUp("JpaMessageHandlerTransactionalParserTests.xml", getClass());
final Proxy proxy = TestUtils.getPropertyValue(this.consumer, "handler", Proxy.class);
assertNotNull(proxy);
AbstractReplyProducingMessageHandler.RequestHandler handler =
TestUtils.getPropertyValue(this.consumer, "handler.advisedRequestHandler",
AbstractReplyProducingMessageHandler.RequestHandler.class);
assertNotNull(handler);
assertTrue(AopUtils.isAopProxy(handler));
}
@Test

View File

@@ -176,7 +176,8 @@ public class JpaOutboundGatewayParserTests extends AbstractRequestHandlerAdvice
public void advised() throws Throwable {
setUp("JpaOutboundGatewayParserTests.xml", getClass(), "advised");
MessageHandler jpaOutboundGateway = context.getBean("advised.handler", MessageHandler.class);
EventDrivenConsumer jpaOutboundGatewayEndpoint = context.getBean("advised", EventDrivenConsumer.class);
MessageHandler jpaOutboundGateway = TestUtils.getPropertyValue(jpaOutboundGatewayEndpoint, "handler", MessageHandler.class);
FooAdvice advice = context.getBean("jpaFooAdvice", FooAdvice.class);
assertTrue(AopUtils.isAopProxy(jpaOutboundGateway));

View File

@@ -1,11 +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:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-jpa="http://www.springframework.org/schema/integration/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/jpa http://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd">

View File

@@ -1,23 +1,17 @@
<?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:jpa="http://www.springframework.org/schema/integration/jpa"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:int="http://www.springframework.org/schema/integration"
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/jpa http://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/integration/jpa"
xmlns:int="http://www.springframework.org/schema/integration"
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/jpa http://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd">
<import resource="BaseJpaPollingChannelAdapterTests-context.xml"/>
<import resource="BaseJpaPollingChannelAdapterTests-context.xml" />
<int:chain input-channel="jpaOutboundChannelAdapterWithinChain">
<jpa:outbound-channel-adapter entity-manager="entityManager" persist-mode="PERSIST">
<jpa:transactional/>
<jpa:transactional />
</jpa:outbound-channel-adapter>
</int:chain>

View File

@@ -658,6 +658,7 @@
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="3">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
minOccurs="0" maxOccurs="1" />
</xsd:choice>
@@ -775,6 +776,7 @@
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
minOccurs="0" maxOccurs="1" />
</xsd:choice>

View File

@@ -82,6 +82,7 @@
<xsd:extension base="gatewayType">
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
minOccurs="0" maxOccurs="1" />
</xsd:choice>

View File

@@ -12,8 +12,13 @@
<int:channel id="good" />
<int-rmi:outbound-gateway remote-channel="foo" host="localhost"
request-channel="good" reply-channel="reply"
port="#{@port}"/>
request-channel="good" reply-channel="reply" port="#{@port}">
<int-rmi:transactional/>
</int-rmi:outbound-gateway>
<bean id="transactionManager" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.transaction.PlatformTransactionManager"/>
</bean>
<int-rmi:inbound-gateway request-channel="foo" registry-port="#{@port}" />

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-2016 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.
@@ -21,25 +21,31 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.Message;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
/**
* @author Gary Russell
* @author Artem Bilan
* @since 3.0
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@DirtiesContext
public class BackToBackTests {
@Autowired
@@ -57,12 +63,17 @@ public class BackToBackTests {
@Autowired
private AbstractApplicationContext context;
@Autowired
private PlatformTransactionManager transactionManager;
@Test
public void testGood() {
good.send(new GenericMessage<String>("foo"));
good.send(new GenericMessage<>("foo"));
Message<?> reply = this.reply.receive(0);
assertNotNull(reply);
assertEquals("reply:foo", reply.getPayload());
verify(this.transactionManager).getTransaction(any(TransactionDefinition.class));
}
@Test

View File

@@ -0,0 +1,8 @@
log4j.rootCategory=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n
log4j.category.org.springframework.integration=WARN
log4j.category.org.springframework.integration.rmi=WARN

View File

@@ -209,6 +209,7 @@
<xsd:extension base="base-outbound-adapter-type">
<xsd:all>
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
minOccurs="0" maxOccurs="1" />
</xsd:all>

View File

@@ -4,6 +4,6 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n
log4j.category.com.jcraft.jsch=DEBUG
log4j.category.org.springframework.integration=DEBUG
log4j.category.org.springframework.integration.sftp=DEBUG
log4j.category.com.jcraft.jsch=WARN
log4j.category.org.springframework.integration=WARN
log4j.category.org.springframework.integration.sftp=WARN

View File

@@ -292,6 +292,7 @@
<xsd:complexType name="outbound-twitter-type">
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType"
minOccurs="0" maxOccurs="1" />
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>

View File

@@ -31,6 +31,7 @@
<xsd:sequence>
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
<xsd:element name="uri-variable" type="uriVariableType" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string">

View File

@@ -487,6 +487,79 @@ Note, however, that in that case, the entire downstream flow would be within the
In the case of a `MessageHandler` that does **not** return a response, the advice chain order is retained.
[[tx-handle-message-advice]]
==== Transaction Support
Starting with _version 5.0_ a new `TransactionHandleMessageAdvice` has been introduced to make the whole downstream flow transactional, thanks to the `HandleMessageAdvice` implementation.
When regular `TransactionInterceptor` is used in the `<request-handler-advice-chain>`, for example via `<tx:advice>` configuration, a started transaction is only applied only for an internal `AbstractReplyProducingMessageHandler.handleRequestMessage()` and isn't propagated to the downstream flow.
To simplify XML configuration, alongside with the `<request-handler-advice-chain>`, a `<transactional>` sub-element has been added to all `<outbound-gateway>` and `<service-activator>` & family components:
[source,xml]
----
<int-rmi:outbound-gateway remote-channel="foo" host="localhost"
request-channel="good" reply-channel="reply" port="#{@port}">
<int-rmi:transactional/>
</int-rmi:outbound-gateway>
<bean id="transactionManager" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.transaction.PlatformTransactionManager"/>
</bean>
----
For whom is familiar with <<jpa, JPA Integration components>> such a configuration isn't new, but now we can start transaction from any point in our flow, not only from the `<poller>` or Message Driven Channel Adapter like in <<jms-message-driven-channel-adapter, JMS>>.
Java & Annotation configuration can be simplified via newly introduced `TransactionInterceptorBuilder` and the result bean name can be used in the <<annotations, Messaging Annotations>> `adviceChain` attribute:
[source,java]
----
@Bean
public ConcurrentMetadataStore store() {
return new SimpleMetadataStore(hazelcastInstance()
.getMap("idempotentReceiverMetadataStore"));
}
@Bean
public IdempotentReceiverInterceptor idempotentReceiverInterceptor() {
return new IdempotentReceiverInterceptor(
new MetadataStoreSelector(
message -> message.getPayload().toString(),
message -> message.getPayload().toString().toUpperCase(), store()));
}
@Bean
public TransactionInterceptor transactionInterceptor() {
return new TransactionInterceptorBuilder(true)
.transactionManager(this.transactionManager)
.isolation(Isolation.READ_COMMITTED)
.propagation(Propagation.REQUIRES_NEW)
.build();
}
@Bean
@org.springframework.integration.annotation.Transformer(inputChannel = "input",
outputChannel = "output",
adviceChain = { "idempotentReceiverInterceptor",
"transactionInterceptor" })
public Transformer transformer() {
return message -> message;
}
----
Note the `true` for the `TransactionInterceptorBuilder` constructor, which means produce `TransactionHandleMessageAdvice`, not regular `TransactionInterceptor`.
Java DSL supports such an `Advice` via `.transactional()` options on the endpoint configuration:
[source,java]
----
@Bean
public IntegrationFlow updatingGatewayFlow() {
return f -> f
.handle(Jpa.updatingGateway(this.entityManagerFactory),
e -> e.transactional(true))
.channel(c -> c.queue("persistResults"));
}
----
[[advising-filters]]
==== Advising Filters
@@ -510,11 +583,11 @@ An example with the discard being performed after the advice is shown below.
@MessageEndpoint
public class MyAdvisedFilter {
@Filter(inputChannel="input", outputChannel="output",
adviceChain="adviceChain", discardWithinAdvice="false")
public boolean filter(String s) {
return s.contains("good");
}
@Filter(inputChannel="input", outputChannel="output",
adviceChain="adviceChain", discardWithinAdvice="false")
public boolean filter(String s) {
return s.contains("good");
}
}
----

View File

@@ -18,6 +18,9 @@ development process.
The `@Poller` annotation now has the `errorChannel` attribute for easier configuration of the underlying `MessagePublishingErrorHandler`.
See <<annotations>> for more information.
All the request-reply endpoints (based on `AbstractReplyProducingMessageHandler`) can now start transaction and, therefore, make the whole downstream flow transactional.
See <<tx-handle-message-advice>> for more information.
==== JMS Changes
Previously, Spring Integration JMS XML configuration used a default bean name `connectionFactory` for the JMS Connection Factory, allowing the property to be omitted from component definitions.