Merge pull request #176 from olegz/INT-2213

added support for specifying 'temporary-remote-directory' as well as 'temporary-remote-directory-expression'
This commit is contained in:
Mark Fisher
2011-11-10 15:36:37 -05:00
9 changed files with 128 additions and 49 deletions

View File

@@ -16,9 +16,6 @@
package org.springframework.integration.handler; package org.springframework.integration.handler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.integration.Message; import org.springframework.integration.Message;
import org.springframework.integration.MessageHandlingException; import org.springframework.integration.MessageHandlingException;
@@ -41,8 +38,6 @@ import org.springframework.util.Assert;
*/ */
public abstract class AbstractMessageHandler extends IntegrationObjectSupport implements MessageHandler, TrackableComponent, Orderable { public abstract class AbstractMessageHandler extends IntegrationObjectSupport implements MessageHandler, TrackableComponent, Orderable {
protected final Log logger = LogFactory.getLog(this.getClass());
private volatile boolean shouldTrack = false; private volatile boolean shouldTrack = false;
private volatile int order = Ordered.LOWEST_PRECEDENCE; private volatile int order = Ordered.LOWEST_PRECEDENCE;

View File

@@ -16,8 +16,6 @@
package org.springframework.integration.file.config; package org.springframework.integration.file.config;
import org.w3c.dom.Element;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanDefinitionStoreException;
@@ -26,10 +24,15 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
import org.springframework.integration.file.remote.session.CachingSessionFactory; import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/** /**
* @author Oleg Zhurakousky * @author Oleg Zhurakousky
@@ -40,8 +43,7 @@ public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChan
private final Log logger = LogFactory.getLog(this.getClass()); private final Log logger = LogFactory.getLog(this.getClass());
@Override @Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder handlerBuilder = BeanDefinitionBuilder.genericBeanDefinition( BeanDefinitionBuilder handlerBuilder = BeanDefinitionBuilder.genericBeanDefinition(FileTransferringMessageHandler.class);
"org.springframework.integration.file.remote.handler.FileTransferringMessageHandler");
// This whole block must be refactored once cache-session attribute is removed // This whole block must be refactored once cache-session attribute is removed
String sessionFactoryName = element.getAttribute("session-factory"); String sessionFactoryName = element.getAttribute("session-factory");
@@ -70,26 +72,8 @@ public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChan
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "temporary-file-suffix"); IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "temporary-file-suffix");
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "auto-create-directory"); IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "auto-create-directory");
// configure remote directory expression this.configureRemoteDirectories(element, handlerBuilder);
String remoteDirectory = element.getAttribute("remote-directory");
String remoteDirectoryExpression = element.getAttribute("remote-directory-expression");
boolean hasRemoteDirectory = StringUtils.hasText(remoteDirectory);
boolean hasRemoteDirectoryExpression = StringUtils.hasText(remoteDirectoryExpression);
if (!(hasRemoteDirectory ^ hasRemoteDirectoryExpression)) {
throw new BeanDefinitionStoreException("exactly one of 'remote-directory' or 'remote-directory-expression' " +
"is required on a remote file outbound adapter");
}
BeanDefinition remoteDirectoryExpressionDefinition = null;
if (hasRemoteDirectory) {
remoteDirectoryExpressionDefinition = new RootBeanDefinition("org.springframework.expression.common.LiteralExpression");
remoteDirectoryExpressionDefinition.getConstructorArgumentValues().addGenericArgumentValue(remoteDirectory);
}
else if (hasRemoteDirectoryExpression) {
remoteDirectoryExpressionDefinition = new RootBeanDefinition("org.springframework.integration.config.ExpressionFactoryBean");
remoteDirectoryExpressionDefinition.getConstructorArgumentValues().addGenericArgumentValue(remoteDirectoryExpression);
}
handlerBuilder.addPropertyValue("remoteDirectoryExpression", remoteDirectoryExpressionDefinition);
// configure remote FileNameGenerator // configure remote FileNameGenerator
String remoteFileNameGenerator = element.getAttribute("remote-filename-generator"); String remoteFileNameGenerator = element.getAttribute("remote-filename-generator");
String remoteFileNameGeneratorExpression = element.getAttribute("remote-filename-generator-expression"); String remoteFileNameGeneratorExpression = element.getAttribute("remote-filename-generator-expression");
@@ -104,8 +88,7 @@ public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChan
handlerBuilder.addPropertyReference("fileNameGenerator", remoteFileNameGenerator); handlerBuilder.addPropertyReference("fileNameGenerator", remoteFileNameGenerator);
} }
else { else {
BeanDefinitionBuilder fileNameGeneratorBuilder = BeanDefinitionBuilder.genericBeanDefinition( BeanDefinitionBuilder fileNameGeneratorBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultFileNameGenerator.class);
"org.springframework.integration.file.DefaultFileNameGenerator");
fileNameGeneratorBuilder.addPropertyValue("expression", remoteFileNameGeneratorExpression); fileNameGeneratorBuilder.addPropertyValue("expression", remoteFileNameGeneratorExpression);
handlerBuilder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition()); handlerBuilder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition());
} }
@@ -114,5 +97,38 @@ public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChan
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "remote-file-separator"); IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "remote-file-separator");
return handlerBuilder.getBeanDefinition(); return handlerBuilder.getBeanDefinition();
} }
private void configureRemoteDirectories(Element element, BeanDefinitionBuilder handlerBuilder){
this.doConfigureRemoteDirectory(element, handlerBuilder, "remote-directory", "remote-directory-expression", "remoteDirectoryExpression", true);
this.doConfigureRemoteDirectory(element, handlerBuilder, "temporary-remote-directory", "temporary-remote-directory-expression", "temporaryRemoteDirectoryExpression", false);
}
private void doConfigureRemoteDirectory(Element element, BeanDefinitionBuilder handlerBuilder,
String directoryAttribute, String directoryExpressionAttribute,
String directoryExpressionPropertyName, boolean atLeastOneRequired){
String remoteDirectory = element.getAttribute(directoryAttribute);
String remoteDirectoryExpression = element.getAttribute(directoryExpressionAttribute);
boolean hasRemoteDirectory = StringUtils.hasText(remoteDirectory);
boolean hasRemoteDirectoryExpression = StringUtils.hasText(remoteDirectoryExpression);
if (atLeastOneRequired){
if (!(hasRemoteDirectory ^ hasRemoteDirectoryExpression)) {
throw new BeanDefinitionStoreException("exactly one of '" + directoryAttribute + "' or '" + directoryExpressionAttribute + "' " +
"is required on a remote file outbound adapter");
}
}
BeanDefinition remoteDirectoryExpressionDefinition = null;
if (hasRemoteDirectory) {
remoteDirectoryExpressionDefinition = new RootBeanDefinition(LiteralExpression.class);
remoteDirectoryExpressionDefinition.getConstructorArgumentValues().addGenericArgumentValue(remoteDirectory);
}
else if (hasRemoteDirectoryExpression) {
remoteDirectoryExpressionDefinition = new RootBeanDefinition(ExpressionFactoryBean.class);
remoteDirectoryExpressionDefinition.getConstructorArgumentValues().addGenericArgumentValue(remoteDirectoryExpression);
}
if (remoteDirectoryExpressionDefinition != null){
handlerBuilder.addPropertyValue(directoryExpressionPropertyName, remoteDirectoryExpressionDefinition);
}
}
} }

View File

@@ -53,6 +53,8 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
private volatile boolean autoCreateDirectory = false; private volatile boolean autoCreateDirectory = false;
private volatile ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor; private volatile ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor;
private volatile ExpressionEvaluatingMessageProcessor<String> temporaryDirectoryExpressionProcessor;
private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator(); private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
@@ -83,6 +85,11 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
this.directoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor<String>(remoteDirectoryExpression, String.class); this.directoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor<String>(remoteDirectoryExpression, String.class);
} }
public void setTemporaryRemoteDirectoryExpression(Expression temporaryRemoteDirectoryExpression) {
Assert.notNull(temporaryRemoteDirectoryExpression, "temporaryRemoteDirectoryExpression must not be null");
this.temporaryDirectoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor<String>(temporaryRemoteDirectoryExpression, String.class);
}
protected String getTemporaryFileSuffix() { protected String getTemporaryFileSuffix() {
return this.temporaryFileSuffix; return this.temporaryFileSuffix;
} }
@@ -116,8 +123,12 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
Session session = this.sessionFactory.getSession(); Session session = this.sessionFactory.getSession();
try { try {
String remoteDirectory = this.directoryExpressionProcessor.processMessage(message); String remoteDirectory = this.directoryExpressionProcessor.processMessage(message);
String temporaryRemoteDirectory = remoteDirectory;
if (this.temporaryDirectoryExpressionProcessor != null){
temporaryRemoteDirectory = this.directoryExpressionProcessor.processMessage(message);
}
String fileName = this.fileNameGenerator.generateFileName(message); String fileName = this.fileNameGenerator.generateFileName(message);
this.sendFileToRemoteDirectory(file, remoteDirectory, fileName, session); this.sendFileToRemoteDirectory(file, temporaryRemoteDirectory, remoteDirectory, fileName, session);
} }
catch (FileNotFoundException e) { catch (FileNotFoundException e) {
throw new MessageDeliveryException(message, throw new MessageDeliveryException(message,
@@ -180,19 +191,19 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
} }
} }
private void sendFileToRemoteDirectory(File file, String remoteDirectory, String fileName, Session session) private void sendFileToRemoteDirectory(File file, String temporaryRemoteDirectory, String remoteDirectory, String fileName, Session session)
throws FileNotFoundException, IOException { throws FileNotFoundException, IOException {
FileInputStream fileInputStream = new FileInputStream(file); FileInputStream fileInputStream = new FileInputStream(file);
if (!StringUtils.hasText(remoteDirectory)) {
remoteDirectory = ""; remoteDirectory = this.normalizeDirectoryPath(remoteDirectory);
} temporaryRemoteDirectory = this.normalizeDirectoryPath(temporaryRemoteDirectory);
else if (!remoteDirectory.endsWith(this.remoteFileSeparator)) {
remoteDirectory += this.remoteFileSeparator;
}
String remoteFilePath = remoteDirectory + fileName; String remoteFilePath = remoteDirectory + fileName;
String tempRemoteFilePath = temporaryRemoteDirectory + fileName;
// write remote file first with .writing extension // write remote file first with .writing extension
String tempFilePath = remoteFilePath + this.temporaryFileSuffix; String tempFilePath = tempRemoteFilePath + this.temporaryFileSuffix;
if (this.autoCreateDirectory) { if (this.autoCreateDirectory) {
session.mkdir(remoteDirectory); session.mkdir(remoteDirectory);
} }
@@ -208,5 +219,15 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
fileInputStream.close(); fileInputStream.close();
} }
} }
private String normalizeDirectoryPath(String directoryPath){
if (!StringUtils.hasText(directoryPath)) {
directoryPath = "";
}
else if (!directoryPath.endsWith(this.remoteFileSeparator)) {
directoryPath += this.remoteFileSeparator;
}
return directoryPath;
}
} }

View File

@@ -24,15 +24,26 @@
type="xsd:string"> type="xsd:string">
<xsd:annotation> <xsd:annotation>
<xsd:documentation> <xsd:documentation>
Allows you to provide SpEL expression which Allows you to provide a SpEL expression which
will compute directory will compute the directory
path path where the files will be transferred to
where file will be transferred TO
(e.g., "headers.['remote_dir'] + (e.g., "headers.['remote_dir'] +
'/myTransfers'"); '/myTransfers'");
</xsd:documentation> </xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
<xsd:attribute name="temporary-remote-directory-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide a SpEL expression which
will compute the temporary directory
path where files will be transferred to before they are moved to the remote-directory
(e.g., "headers.['remote_dir'] +
'/temp/myTransfers'");
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-create-directory" type="xsd:string" <xsd:attribute name="auto-create-directory" type="xsd:string"
default="false"> default="false">
<xsd:annotation> <xsd:annotation>
@@ -365,7 +376,15 @@
use="optional"> use="optional">
<xsd:annotation> <xsd:annotation>
<xsd:documentation> <xsd:documentation>
Identifies the directory path (e.g., "/temp/mytransfers") Identifies the remote directory path (e.g., "/remote/mytransfers")
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="temporary-remote-directory" type="xsd:string"
use="optional">
<xsd:annotation>
<xsd:documentation>
Identifies the remote temporary directory path (e.g., "/remote/temp/mytransfers")
</xsd:documentation> </xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>

View File

@@ -27,6 +27,7 @@
session-factory="ftpSessionFactory" session-factory="ftpSessionFactory"
cache-sessions="false" cache-sessions="false"
remote-directory="foo/bar" remote-directory="foo/bar"
temporary-remote-directory="baz/abc"
charset="UTF-8" charset="UTF-8"
remote-file-separator="." remote-file-separator="."
temporary-file-suffix=".foo" temporary-file-suffix=".foo"
@@ -42,6 +43,7 @@
temporary-file-suffix=".foo" temporary-file-suffix=".foo"
remote-filename-generator="fileNameGenerator" remote-filename-generator="fileNameGenerator"
order="12"/> order="12"/>
<int-ftp:outbound-channel-adapter id="simpleAdapter" <int-ftp:outbound-channel-adapter id="simpleAdapter"
channel="ftpChannel" channel="ftpChannel"

View File

@@ -58,6 +58,8 @@ public class FtpOutboundChannelAdapterParserTests {
assertEquals(".", remoteFileSeparator); assertEquals(".", remoteFileSeparator);
assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "fileNameGenerator")); assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "fileNameGenerator"));
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset")); assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset"));
assertNotNull(TestUtils.getPropertyValue(handler, "directoryExpressionProcessor"));
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectoryExpressionProcessor"));
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory")); assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory"));
Object sfProperty = TestUtils.getPropertyValue(handler, "sessionFactory"); Object sfProperty = TestUtils.getPropertyValue(handler, "sessionFactory");
assertEquals(DefaultFtpSessionFactory.class, sfProperty.getClass()); assertEquals(DefaultFtpSessionFactory.class, sfProperty.getClass());

View File

@@ -26,16 +26,27 @@
type="xsd:string"> type="xsd:string">
<xsd:annotation> <xsd:annotation>
<xsd:documentation> <xsd:documentation>
Allows you to provide SpEL expression which Allows you to provide a SpEL expression which
will compute directory will compute the directory
path path
where file will be transferred TO where files will be transferred to
(e.g., "headers.['remote_dir'] + (e.g., "headers.['remote_dir'] +
'/myTransfers'"); '/myTransfers'");
</xsd:documentation> </xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
<xsd:attribute name="temporary-remote-directory-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide a SpEL expression which
will compute the temporary directory
path where files will be transferred to before they are moved to the remote-directory
(e.g., "headers.['remote_dir'] +
'/temp/myTransfers'");
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-create-directory" type="xsd:string" <xsd:attribute name="auto-create-directory" type="xsd:string"
default="false"> default="false">
<xsd:annotation> <xsd:annotation>
@@ -370,6 +381,14 @@
</xsd:documentation> </xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
<xsd:attribute name="temporary-remote-directory" type="xsd:string"
use="optional">
<xsd:annotation>
<xsd:documentation>
Identifies the remote temporary directory path (e.g., "/remote/temp/mytransfers")
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="channel" use="required" type="xsd:string"> <xsd:attribute name="channel" use="required" type="xsd:string">
<xsd:annotation> <xsd:annotation>
<xsd:appinfo> <xsd:appinfo>

View File

@@ -27,6 +27,7 @@
remote-file-separator="." remote-file-separator="."
temporary-file-suffix=".bar" temporary-file-suffix=".bar"
remote-directory="foo/bar" remote-directory="foo/bar"
temporary-remote-directory="foo/baz"
order="23"/> order="23"/>
<int-sftp:outbound-channel-adapter id="sftpOutboundAdapterWithExpression" <int-sftp:outbound-channel-adapter id="sftpOutboundAdapterWithExpression"

View File

@@ -19,6 +19,8 @@ package org.springframework.integration.sftp.config;
import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import java.util.Iterator; import java.util.Iterator;
@@ -65,6 +67,7 @@ public class OutboundChannelAdapterParserTests {
Expression remoteDirectoryExpression = (Expression) TestUtils.getPropertyValue(handler, "directoryExpressionProcessor.expression"); Expression remoteDirectoryExpression = (Expression) TestUtils.getPropertyValue(handler, "directoryExpressionProcessor.expression");
assertNotNull(remoteDirectoryExpression); assertNotNull(remoteDirectoryExpression);
assertTrue(remoteDirectoryExpression instanceof LiteralExpression); assertTrue(remoteDirectoryExpression instanceof LiteralExpression);
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectoryExpressionProcessor"));
assertEquals(context.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "fileNameGenerator")); assertEquals(context.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "fileNameGenerator"));
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset")); assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset"));
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory")); assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory"));
@@ -101,6 +104,7 @@ public class OutboundChannelAdapterParserTests {
assertEquals("payload.getName() + '-foo'", fileNameGeneratorExpression); assertEquals("payload.getName() + '-foo'", fileNameGeneratorExpression);
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset")); assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset"));
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory")); assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory"));
assertNull(TestUtils.getPropertyValue(handler, "temporaryDirectoryExpressionProcessor"));
} }