INT-2685 Transaction Synchronization
Remove M3 disposition-* attributes on File/(S)FTP inbound adapters. Add <pseudo-transactional/> and <transaction-synchronization/> elements to <poller/>. These elements provide the following attributes: * on-success-expression * on-success-result-channel * on-failure-expression * on-failure-result-channel * send-timeout <transaction-synchronization/> synchronizes these expression evaluations with the transaction, such that they are executed immediately after the commit/rollback. <psuedo-transactional/> is used for a non-transactional poller. When an <advice-chain/> is provided to the poller, <psuedo-transactional/> and <transaction-synchronization/> are synonyms; and the behavior is dictated by whether or not the <advice-chain/> contains a transaction advice. It is recommended that <pseudo-transactional/> is used when the <advice-chain/> does not have a txAdvice, and <transaction-synchronization/> when it does, but the framework does not enforce this. The expressions have the original (polled) message as the #root variable. In addition, a BeanResolver is provided, allowing expressions such as '@someBean.handleSuccess(payload)'. MessageSources may also implement PseudoTransactionalMessageSource. This has a number of methods allowing more flexibility in transactional and non-transactional environents. For example, for backwards compatibility. the mail-inbound-channel-adapter deletes its polled message after the receive() rather than after the polled message is sent (when running in a non-transactional poller). However, when running in a transactional poller, the delete is done after the transaction commits (but not when it rolls back). In addition, MessageSources that implement this interface can optionally provide an arbitrary object to the success/failure expressions in a variable named '#resource'. INT-2685 Polishing PR Review Comments Add tests for non-tx PseudoTransactionalMessageSource
This commit is contained in:
committed by
Oleg Zhurakousky
parent
d6623bda82
commit
4de02fa75e
@@ -35,6 +35,4 @@ public abstract class FileHeaders {
|
||||
|
||||
public static final String REMOTE_FILE = PREFIX + "remoteFile";
|
||||
|
||||
public static final String DISPOSITION_RESULT = PREFIX + "dispositionResult";
|
||||
|
||||
}
|
||||
|
||||
@@ -26,22 +26,14 @@ import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.aggregator.ResequencingMessageGroupProcessor;
|
||||
import org.springframework.integration.context.IntegrationObjectSupport;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.integration.core.PseudoTransactionalMessageSource;
|
||||
import org.springframework.integration.file.filters.AcceptOnceFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.util.ExpressionUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -73,7 +65,7 @@ import org.springframework.util.Assert;
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class FileReadingMessageSource extends IntegrationObjectSupport implements PseudoTransactionalMessageSource<File, FileMessageHolder> {
|
||||
public class FileReadingMessageSource extends IntegrationObjectSupport implements MessageSource<File> {
|
||||
|
||||
private static final int DEFAULT_INTERNAL_QUEUE_CAPACITY = 5;
|
||||
|
||||
@@ -95,17 +87,8 @@ public class FileReadingMessageSource extends IntegrationObjectSupport implement
|
||||
|
||||
private volatile boolean scanEachPoll = false;
|
||||
|
||||
private volatile Expression dispositionExpression;
|
||||
|
||||
private final MessagingTemplate dispositionMessagingTemplate = new MessagingTemplate();
|
||||
|
||||
private volatile boolean dispostionResultChannelSet;
|
||||
|
||||
private final ThreadLocal<FileMessageHolder> resources = new ThreadLocal<FileMessageHolder>();
|
||||
|
||||
private EvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a FileReadingMessageSource with a naturally ordered queue of unbounded capacity.
|
||||
*/
|
||||
@@ -240,21 +223,6 @@ public class FileReadingMessageSource extends IntegrationObjectSupport implement
|
||||
this.scanEachPoll = scanEachPoll;
|
||||
}
|
||||
|
||||
public void setDispositionExpression(Expression dispositionExpression) {
|
||||
Assert.notNull(dispositionExpression, "'dispositionExpression' must not be null");
|
||||
this.dispositionExpression = dispositionExpression;
|
||||
}
|
||||
|
||||
public void setDispositionResultChannel(MessageChannel dispositionResultChannel) {
|
||||
Assert.notNull(dispositionResultChannel, "'dispositionResultChannel' must not be null");
|
||||
this.dispositionMessagingTemplate.setDefaultChannel(dispositionResultChannel);
|
||||
this.dispostionResultChannelSet = true;
|
||||
}
|
||||
|
||||
public void setDispositionSendTimeout(long dispositionSendTimeout) {
|
||||
this.dispositionMessagingTemplate.setSendTimeout(dispositionSendTimeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
return "file:inbound-channel-adapter";
|
||||
@@ -272,10 +240,6 @@ public class FileReadingMessageSource extends IntegrationObjectSupport implement
|
||||
"Source path [" + this.directory + "] does not point to a directory.");
|
||||
Assert.isTrue(this.directory.canRead(),
|
||||
"Source directory [" + this.directory + "] is not readable.");
|
||||
if (getBeanFactory() != null) {
|
||||
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(
|
||||
new BeanFactoryResolver(getBeanFactory()));
|
||||
}
|
||||
}
|
||||
|
||||
public Message<File> receive() throws MessagingException {
|
||||
@@ -345,47 +309,4 @@ public class FileReadingMessageSource extends IntegrationObjectSupport implement
|
||||
}
|
||||
}
|
||||
|
||||
public FileMessageHolder getResource() {
|
||||
FileMessageHolder resource = new FileMessageHolder();
|
||||
this.resources.set(resource);
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void afterCommit(FileMessageHolder resource) {
|
||||
Assert.isInstanceOf(FileMessageHolder.class, resource);
|
||||
FileMessageHolder fileResource = resource;
|
||||
if (this.dispositionExpression != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Executing expression " + this.dispositionExpression.getExpressionString() + " on " +
|
||||
fileResource.getMessage());
|
||||
}
|
||||
Object result = this.dispositionExpression.getValue(this.evaluationContext, fileResource.getMessage());
|
||||
if (result != null) {
|
||||
if (this.dispostionResultChannelSet) {
|
||||
try {
|
||||
Message<File> message = MessageBuilder.fromMessage(fileResource.getMessage())
|
||||
.setHeader(FileHeaders.DISPOSITION_RESULT, result).build();
|
||||
this.dispositionMessagingTemplate.send(message);
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Error sending File Disposition Result", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.resources.set(null);
|
||||
}
|
||||
|
||||
public void afterRollback(FileMessageHolder resource) {
|
||||
// no op
|
||||
}
|
||||
|
||||
public void afterReceiveNoTx(FileMessageHolder resource) {
|
||||
// no op
|
||||
}
|
||||
|
||||
public void afterSendNoTx(FileMessageHolder resource) {
|
||||
this.afterCommit(resource);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst
|
||||
localFileGeneratorExpressionBuilder.addConstructorArgValue(localFileGeneratorExpression);
|
||||
synchronizerBuilder.addPropertyValue("localFilenameGeneratorExpression", localFileGeneratorExpressionBuilder.getBeanDefinition());
|
||||
}
|
||||
FileNamespaceUtils.setDispositionAttributes(element, messageSourceBuilder);
|
||||
return messageSourceBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ public class FileInboundChannelAdapterParser extends AbstractPollingInboundChann
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "directory");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-create-directory");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "queue-size");
|
||||
FileNamespaceUtils.setDispositionAttributes(element, builder);
|
||||
String filterBeanName = this.registerFilter(element, parserContext);
|
||||
String lockerBeanName = registerLocker(element, parserContext);
|
||||
if (lockerBeanName != null) {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.file.config;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.integration.config.ExpressionFactoryBean;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 2.2
|
||||
*
|
||||
*/
|
||||
public class FileNamespaceUtils {
|
||||
|
||||
public static void setDispositionAttributes(Element element, BeanDefinitionBuilder builder) {
|
||||
String dispositionExpression = element.getAttribute("disposition-expression");
|
||||
if (StringUtils.hasText(dispositionExpression)) {
|
||||
RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
|
||||
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(dispositionExpression);
|
||||
builder.addPropertyValue("dispositionExpression", expressionDef);
|
||||
}
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "disposition-result-channel");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "disposition-send-timeout");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,8 +22,6 @@ import java.util.Comparator;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.file.DirectoryScanner;
|
||||
import org.springframework.integration.file.FileReadingMessageSource;
|
||||
import org.springframework.integration.file.filters.CompositeFileListFilter;
|
||||
@@ -57,12 +55,6 @@ public class FileReadingMessageSourceFactoryBean implements FactoryBean<FileRead
|
||||
|
||||
private volatile Integer queueSize;
|
||||
|
||||
private volatile Expression dispositionExpression;
|
||||
|
||||
private volatile MessageChannel dispositionResultChannel;
|
||||
|
||||
private volatile Long dispositionSendTimeout;
|
||||
|
||||
private final Object initializationMonitor = new Object();
|
||||
|
||||
|
||||
@@ -101,18 +93,6 @@ public class FileReadingMessageSourceFactoryBean implements FactoryBean<FileRead
|
||||
this.locker = locker;
|
||||
}
|
||||
|
||||
public void setDispositionExpression(Expression dispositionExpression) {
|
||||
this.dispositionExpression = dispositionExpression;
|
||||
}
|
||||
|
||||
public void setDispositionResultChannel(MessageChannel dispositionResultChannel) {
|
||||
this.dispositionResultChannel = dispositionResultChannel;
|
||||
}
|
||||
|
||||
public void setDispositionSendTimeout(Long dispositionSendTimeout) {
|
||||
this.dispositionSendTimeout = dispositionSendTimeout;
|
||||
}
|
||||
|
||||
public FileReadingMessageSource getObject() throws Exception {
|
||||
if (this.source == null) {
|
||||
initSource();
|
||||
@@ -169,15 +149,6 @@ public class FileReadingMessageSourceFactoryBean implements FactoryBean<FileRead
|
||||
if (this.autoCreateDirectory != null) {
|
||||
this.source.setAutoCreateDirectory(this.autoCreateDirectory);
|
||||
}
|
||||
if (this.dispositionExpression != null) {
|
||||
this.source.setDispositionExpression(this.dispositionExpression);
|
||||
}
|
||||
if (this.dispositionResultChannel != null) {
|
||||
this.source.setDispositionResultChannel(this.dispositionResultChannel);
|
||||
}
|
||||
if (this.dispositionSendTimeout != null) {
|
||||
this.source.setDispositionSendTimeout(this.dispositionSendTimeout);
|
||||
}
|
||||
this.source.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,10 @@ import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.core.PseudoTransactionalMessageSource;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.endpoint.MessageProducerSupport;
|
||||
import org.springframework.integration.file.FileMessageHolder;
|
||||
import org.springframework.integration.file.FileReadingMessageSource;
|
||||
import org.springframework.integration.file.filters.AcceptOnceFileListFilter;
|
||||
import org.springframework.integration.file.filters.CompositeFileListFilter;
|
||||
@@ -59,7 +56,7 @@ import org.springframework.util.Assert;
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public abstract class AbstractInboundFileSynchronizingMessageSource<F> extends MessageProducerSupport
|
||||
implements PseudoTransactionalMessageSource<File, FileMessageHolder> {
|
||||
implements MessageSource<File> {
|
||||
|
||||
/**
|
||||
* Should the endpoint attempt to create the local directory? True by default.
|
||||
@@ -107,18 +104,6 @@ public abstract class AbstractInboundFileSynchronizingMessageSource<F> extends M
|
||||
this.localDirectory = localDirectory;
|
||||
}
|
||||
|
||||
public void setDispositionExpression(Expression dispositionExpression) {
|
||||
this.fileSource.setDispositionExpression(dispositionExpression);
|
||||
}
|
||||
|
||||
public void setDispositionResultChannel(MessageChannel dispositionResultChannel) {
|
||||
this.fileSource.setDispositionResultChannel(dispositionResultChannel);
|
||||
}
|
||||
|
||||
public void setDispositionSendTimeout(long dispositionSendTimeout) {
|
||||
this.fileSource.setDispositionSendTimeout(dispositionSendTimeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
Assert.notNull(this.localDirectory, "localDirectory must not be null");
|
||||
@@ -172,24 +157,4 @@ public abstract class AbstractInboundFileSynchronizingMessageSource<F> extends M
|
||||
new RegexPatternFileListFilter(completePattern)));
|
||||
}
|
||||
|
||||
public FileMessageHolder getResource() {
|
||||
return this.fileSource.getResource();
|
||||
}
|
||||
|
||||
public void afterCommit(FileMessageHolder resource) {
|
||||
this.fileSource.afterCommit(resource);
|
||||
}
|
||||
|
||||
public void afterRollback(FileMessageHolder resource) {
|
||||
this.fileSource.afterRollback(resource);
|
||||
}
|
||||
|
||||
public void afterReceiveNoTx(FileMessageHolder resource) {
|
||||
this.fileSource.afterReceiveNoTx(resource);
|
||||
}
|
||||
|
||||
public void afterSendNoTx(FileMessageHolder resource) {
|
||||
this.fileSource.afterSendNoTx(resource);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -166,42 +166,6 @@ Only files matching this regular expression will be picked up by this adapter.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="disposition-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
SpEL expression to be executed after the message has been sent. If running in a transactional
|
||||
poller, it will be executed after the transaction commits. If running in a non-transactional
|
||||
poller it will execute after the message is sent. Note that the actual point of execution
|
||||
depends on any asynchronous handoffs on the downstream flow. It will be executed when the
|
||||
current thread returns from the channel send. The root object of the expression is the
|
||||
original message (with a File payload). Examples: "payload.delete()",
|
||||
"payload.renameTo('/foo/bar/' + payload.name)", "@someBean.doSomething(payload)".
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="disposition-result-channel" type="xsd:string" default="nullChannel">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
If a 'disposition-expression' is provided, and that expression returns a result, the result
|
||||
is sent to this channel, with the original message payload and a 'file_dispositionResult'
|
||||
header containing the result of the expression execution.
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.MessageChannel"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="disposition-send-timeout" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
If a 'disposition-expression' is provided, and that expression returns a result, the result
|
||||
is sent to this disposition-result-channel. This timeout specifies how long to wait if
|
||||
that channel might block (such as a bounded queue channel that is full). Default infinity.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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-file="http://www.springframework.org/schema/integration/file"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/file http://www.springframework.org/schema/integration/file/spring-integration-file-2.2.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
|
||||
|
||||
<context:property-placeholder/>
|
||||
|
||||
<int-file:inbound-channel-adapter id="pseudoTx" channel="input" auto-startup="false"
|
||||
directory="${java.io.tmpdir}/si-test1">
|
||||
<int:poller fixed-rate="500">
|
||||
<int:psuedo-transactional on-success-expression="payload.delete()"
|
||||
on-success-result-channel="successChannel"
|
||||
on-failure-expression="'foo'"
|
||||
on-failure-result-channel="failureChannel"
|
||||
send-timeout="500" />
|
||||
</int:poller>
|
||||
</int-file:inbound-channel-adapter>
|
||||
|
||||
<int:channel id="input" />
|
||||
|
||||
<int:channel id="successChannel">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<int:channel id="failureChannel">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<int:channel id="txInput" />
|
||||
|
||||
<int-file:inbound-channel-adapter id="realTx" channel="txInput" auto-startup="false"
|
||||
directory="${java.io.tmpdir}/si-test2">
|
||||
<int:poller fixed-rate="500">
|
||||
<int:transactional transaction-manager="txManager" />
|
||||
<int:transaction-synchronization on-success-expression="@txManager.committed"
|
||||
on-success-result-channel="successChannel"
|
||||
on-failure-expression="@txManager.rolledBack"
|
||||
on-failure-result-channel="failureChannel"
|
||||
send-timeout="5000" />
|
||||
</int:poller>
|
||||
</int-file:inbound-channel-adapter>
|
||||
|
||||
<bean id="txManager" class="org.springframework.integration.file.FileInboundTransactionTests$DummyTxManager" />
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.file;
|
||||
|
||||
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.io.File;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.core.MessageHandler;
|
||||
import org.springframework.integration.core.PollableChannel;
|
||||
import org.springframework.integration.core.SubscribableChannel;
|
||||
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
|
||||
import org.springframework.transaction.support.DefaultTransactionStatus;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 2.2
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class FileInboundTransactionTests {
|
||||
|
||||
@Autowired
|
||||
private SourcePollingChannelAdapter pseudoTx;
|
||||
|
||||
@Autowired
|
||||
private SourcePollingChannelAdapter realTx;
|
||||
|
||||
@Autowired
|
||||
private SubscribableChannel input;
|
||||
|
||||
@Autowired
|
||||
private SubscribableChannel txInput;
|
||||
|
||||
@Autowired
|
||||
private PollableChannel successChannel;
|
||||
|
||||
@Autowired
|
||||
private PollableChannel failureChannel;
|
||||
|
||||
@Autowired
|
||||
private DummyTxManager transactionManager;
|
||||
|
||||
@Value("${java.io.tmpdir}")
|
||||
private String tmpDir;
|
||||
|
||||
@Test
|
||||
public void testNoTx() throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicBoolean crash = new AtomicBoolean();
|
||||
input.subscribe(new MessageHandler() {
|
||||
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
System.out.println(message);
|
||||
if (crash.get()) {
|
||||
throw new MessagingException("eek");
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
pseudoTx.start();
|
||||
new File(tmpDir + "/si-test1").mkdir();
|
||||
File file = new File(tmpDir + "/si-test1/foo");
|
||||
file.createNewFile();
|
||||
Message<?> result = successChannel.receive(10000);
|
||||
assertNotNull(result);
|
||||
assertEquals(Boolean.TRUE, result.getHeaders().get(MessageHeaders.DISPOSITION_RESULT));
|
||||
System.out.println(result);
|
||||
assertFalse(file.delete());
|
||||
crash.set(true);
|
||||
file = new File(tmpDir + "/si-test1/bar");
|
||||
file.createNewFile();
|
||||
result = failureChannel.receive(10000);
|
||||
assertNotNull(result);
|
||||
System.out.println(result);
|
||||
assertTrue(file.delete());
|
||||
assertEquals("foo", result.getHeaders().get(MessageHeaders.DISPOSITION_RESULT));
|
||||
pseudoTx.stop();
|
||||
assertFalse(transactionManager.getCommitted());
|
||||
assertFalse(transactionManager.getRolledBack());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTx() throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicBoolean crash = new AtomicBoolean();
|
||||
txInput.subscribe(new MessageHandler() {
|
||||
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
System.out.println(message);
|
||||
if (crash.get()) {
|
||||
throw new MessagingException("eek");
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
realTx.start();
|
||||
new File(tmpDir + "/si-test2").mkdir();
|
||||
File file = new File(tmpDir + "/si-test2/baz");
|
||||
file.createNewFile();
|
||||
Message<?> result = successChannel.receive(10000);
|
||||
assertNotNull(result);
|
||||
assertEquals(Boolean.TRUE, result.getHeaders().get(MessageHeaders.DISPOSITION_RESULT));
|
||||
assertTrue(file.delete());
|
||||
System.out.println(result);
|
||||
assertTrue(transactionManager.getCommitted());
|
||||
crash.set(true);
|
||||
file = new File(tmpDir + "/si-test2/qux");
|
||||
file.createNewFile();
|
||||
result = failureChannel.receive(10000);
|
||||
assertNotNull(result);
|
||||
System.out.println(result);
|
||||
assertTrue(file.delete());
|
||||
assertEquals(Boolean.TRUE, result.getHeaders().get(MessageHeaders.DISPOSITION_RESULT));
|
||||
realTx.stop();
|
||||
assertTrue(transactionManager.getRolledBack());
|
||||
}
|
||||
|
||||
public static class DummyTxManager extends AbstractPlatformTransactionManager {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
boolean committed;
|
||||
|
||||
boolean rolledBack;
|
||||
|
||||
@Override
|
||||
protected Object doGetTransaction() throws TransactionException {
|
||||
return new Object();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
|
||||
committed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
|
||||
rolledBack = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluated in transactional onSuccessExpression - ensures we rolled back before evaluation
|
||||
* @return
|
||||
*/
|
||||
public boolean getCommitted() {
|
||||
return committed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluated in transactional onFailureExpression - ensures we rolled back before evaluation
|
||||
* @return
|
||||
*/
|
||||
public boolean getRolledBack() {
|
||||
return rolledBack;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,10 +37,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
|
||||
/**
|
||||
* @author Iwein Fuld
|
||||
@@ -159,17 +156,4 @@ public class FileReadingMessageSourceTests {
|
||||
verify(inputDirectoryMock, times(2)).listFiles();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disposition() {
|
||||
source.setDispositionExpression(new LiteralExpression("foo"));
|
||||
QueueChannel channel = new QueueChannel();
|
||||
source.setDispositionResultChannel(channel);
|
||||
FileMessageHolder resource = source.getResource();
|
||||
File file = mock(File.class);
|
||||
resource.setMessage(new GenericMessage<File>(file));
|
||||
source.afterCommit(resource);
|
||||
Message<?> result = channel.receive(10000);
|
||||
assertSame(file, result.getPayload());
|
||||
assertEquals("foo", result.getHeaders().get(FileHeaders.DISPOSITION_RESULT));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
<!-- under test -->
|
||||
<file:inbound-channel-adapter
|
||||
directory="#{inputDirectory.path}"
|
||||
disposition-expression="payload.delete()"
|
||||
disposition-result-channel="resultChannel"
|
||||
channel="fileMessages" filter="compositeFilter"/>
|
||||
|
||||
<bean id="temp" class="org.junit.rules.TemporaryFolder"
|
||||
@@ -37,7 +35,10 @@
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<si:poller default="true" fixed-rate="10"/>
|
||||
<si:poller default="true" fixed-rate="10">
|
||||
<si:psuedo-transactional on-success-expression="payload.delete()"
|
||||
on-success-result-channel="resultChannel" />
|
||||
</si:poller>
|
||||
|
||||
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.core.PollableChannel;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -58,7 +59,7 @@ public class FileToChannelIntegrationTests {
|
||||
assertNotNull(received.getPayload());
|
||||
Message<?> result = resultChannel.receive(10000);
|
||||
assertNotNull(result);
|
||||
assertEquals(Boolean.TRUE, result.getHeaders().get(FileHeaders.DISPOSITION_RESULT));
|
||||
assertEquals(Boolean.TRUE, result.getHeaders().get(MessageHeaders.DISPOSITION_RESULT));
|
||||
assertTrue(!file.exists());
|
||||
}
|
||||
|
||||
|
||||
@@ -14,14 +14,17 @@
|
||||
directory="${java.io.tmpdir}"
|
||||
filter="filter"
|
||||
comparator="testComparator"
|
||||
disposition-expression="payload.delete()"
|
||||
disposition-result-channel="resultChannel"
|
||||
disposition-send-timeout="123"
|
||||
auto-startup="false">
|
||||
<integration:poller fixed-rate="5000"/>
|
||||
<integration:poller fixed-rate="5000">
|
||||
<integration:psuedo-transactional on-success-expression="payload.delete()"
|
||||
on-success-result-channel="successChannel"
|
||||
on-failure-expression="'foo'"
|
||||
on-failure-result-channel="nullChannel"
|
||||
send-timeout="5000" />
|
||||
</integration:poller>
|
||||
</inbound-channel-adapter>
|
||||
|
||||
<integration:channel id="resultChannel" />
|
||||
<integration:channel id="successChannel" />
|
||||
|
||||
<beans:bean id="filter" class="org.springframework.integration.file.config.FileListFilterFactoryBean"/>
|
||||
|
||||
|
||||
@@ -30,13 +30,10 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.integration.channel.AbstractMessageChannel;
|
||||
import org.springframework.integration.file.DefaultDirectoryScanner;
|
||||
import org.springframework.integration.file.FileReadingMessageSource;
|
||||
import org.springframework.integration.file.filters.AcceptOnceFileListFilter;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@@ -94,15 +91,6 @@ public class FileInboundChannelAdapterParserTests {
|
||||
assertSame("comparator reference not set, ", expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disposition() throws Exception {
|
||||
Object dispositionExpression = accessor.getPropertyValue("dispositionExpression");
|
||||
assertEquals(SpelExpression.class, dispositionExpression.getClass());
|
||||
assertEquals("payload.delete()", ((Expression) dispositionExpression).getExpressionString());
|
||||
assertSame(TestUtils.getPropertyValue(source, "dispositionMessagingTemplate.defaultChannel"), context.getBean("resultChannel"));
|
||||
assertEquals(123L, TestUtils.getPropertyValue(source, "dispositionMessagingTemplate.sendTimeout"));
|
||||
}
|
||||
|
||||
static class TestComparator implements Comparator<File> {
|
||||
|
||||
public int compare(File f1, File f2) {
|
||||
|
||||
Reference in New Issue
Block a user