INT-2633 Add File Disposition to (S)FTP Inbound

FileReadingMessageSource supports disposition of the payload
after the message is sent (or via transaction synchronization).

The (S)FTP adapters delegate to an FRMS; add support for the
file disposition expression, result channel, and send timeout
to the (S)FTP adapters.

Also tested with ftp sample - INTSAMPLES-83 - patch will be
committed once this is in a milestone.
This commit is contained in:
Gary Russell
2012-07-06 13:02:55 -04:00
committed by Oleg Zhurakousky
parent d0f26cd618
commit e6e43100b5
15 changed files with 271 additions and 72 deletions

View File

@@ -0,0 +1,43 @@
/*
* 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 java.io.File;
import org.springframework.integration.Message;
/**
* A simple wrapper for a Message<File>; used for
* file disposition after the send completes, or
* after the transaction commits with a transactional
* poller.
* @author Gary Russell
* @since 2.2
*
*/
public class FileMessageHolder {
private Message<File> message;
Message<File> getMessage() {
return message;
}
void setMessage(Message<File> message) {
this.message = message;
}
}

View File

@@ -38,7 +38,6 @@ 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.FileReadingMessageSource.FileMessageHolder;
import org.springframework.integration.file.filters.AcceptOnceFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.support.MessageBuilder;
@@ -389,16 +388,4 @@ public class FileReadingMessageSource extends IntegrationObjectSupport implement
this.afterCommit(resource);
}
class FileMessageHolder {
private Message<File> message;
Message<File> getMessage() {
return message;
}
void setMessage(Message<File> message) {
this.message = message;
}
}
}

View File

@@ -16,8 +16,6 @@
package org.springframework.integration.file.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
@@ -26,10 +24,11 @@ import org.springframework.integration.config.xml.AbstractPollingInboundChannelA
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.file.remote.session.SessionFactoryFactoryBean;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Abstract base class for parsing remote file inbound channel adapters.
*
*
* @author Oleg Zhurakousky
* @author Mark Fisher
* @since 2.0
@@ -44,9 +43,9 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst
BeanDefinitionBuilder sessionFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(SessionFactoryFactoryBean.class);
sessionFactoryBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
sessionFactoryBuilder.addConstructorArgValue(element.getAttribute("cache-sessions"));
synchronizerBuilder.addConstructorArgValue(sessionFactoryBuilder.getBeanDefinition());
// configure the InboundFileSynchronizer properties
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "remote-directory");
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "delete-remote-files");
@@ -71,6 +70,7 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst
localFileGeneratorExpressionBuilder.addConstructorArgValue(localFileGeneratorExpression);
synchronizerBuilder.addPropertyValue("localFilenameGeneratorExpression", localFileGeneratorExpressionBuilder.getBeanDefinition());
}
FileNamespaceUtils.setDispositionAttributes(element, messageSourceBuilder);
return messageSourceBuilder.getBeanDefinition();
}

View File

@@ -21,9 +21,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.file.locking.NioFileLocker;
@@ -49,14 +47,7 @@ public class FileInboundChannelAdapterParser extends AbstractPollingInboundChann
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "directory");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-create-directory");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "queue-size");
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");
FileNamespaceUtils.setDispositionAttributes(element, builder);
String filterBeanName = this.registerFilter(element, parserContext);
String lockerBeanName = registerLocker(element, parserContext);
if (lockerBeanName != null) {

View File

@@ -0,0 +1,43 @@
/*
* 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");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -22,10 +22,13 @@ 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.MessageSource;
import org.springframework.integration.core.PseudoTransactionalMessageSource;
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;
@@ -50,11 +53,13 @@ import org.springframework.util.Assert;
* {@link AbstractInboundFileSynchronizer}. The synchronizer must
* handle the work of actually connecting to the remote file system and
* delivering new {@link File}s.
*
*
* @author Josh Long
* @author Oleg Zhurakousky
* @author Gary Russell
*/
public abstract class AbstractInboundFileSynchronizingMessageSource<F> extends MessageProducerSupport implements MessageSource<File> {
public abstract class AbstractInboundFileSynchronizingMessageSource<F> extends MessageProducerSupport
implements PseudoTransactionalMessageSource<File, FileMessageHolder> {
/**
* Should the endpoint attempt to create the local directory? True by default.
@@ -81,7 +86,7 @@ public abstract class AbstractInboundFileSynchronizingMessageSource<F> extends M
public AbstractInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<F> synchronizer) {
this(synchronizer, null);
}
public AbstractInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<F> synchronizer, Comparator<File> comparator) {
Assert.notNull(synchronizer, "synchronizer must not be null");
this.synchronizer = synchronizer;
@@ -90,7 +95,7 @@ public abstract class AbstractInboundFileSynchronizingMessageSource<F> extends M
}
else {
this.fileSource = new FileReadingMessageSource(comparator);
}
}
}
@@ -102,6 +107,18 @@ 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");
@@ -155,4 +172,24 @@ 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);
}
}

View File

@@ -40,7 +40,6 @@ 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.file.FileReadingMessageSource.FileMessageHolder;
import org.springframework.integration.message.GenericMessage;
/**