Merge pull request #388 from olegz/INT-2489
This commit is contained in:
@@ -36,6 +36,7 @@ import org.springframework.util.StringUtils;
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Mark Fisher
|
||||
* @author David Turanski
|
||||
* @since 2.0
|
||||
*/
|
||||
public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
|
||||
@@ -49,13 +50,15 @@ public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChan
|
||||
sessionFactoryBuilder.addConstructorArgValue(element.getAttribute("cache-sessions"));
|
||||
|
||||
handlerBuilder.addConstructorArgValue(sessionFactoryBuilder.getBeanDefinition());
|
||||
|
||||
// configure MessageHandler properties
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "temporary-file-suffix");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "use-temporary-file-name");
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "auto-create-directory");
|
||||
|
||||
this.configureRemoteDirectories(element, handlerBuilder);
|
||||
|
||||
|
||||
// configure remote FileNameGenerator
|
||||
String remoteFileNameGenerator = element.getAttribute("remote-filename-generator");
|
||||
String remoteFileNameGeneratorExpression = element.getAttribute("remote-filename-generator-expression");
|
||||
|
||||
@@ -44,18 +44,21 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Fisher
|
||||
* @author Josh Long
|
||||
* @author Oleg Zhurakousky
|
||||
* @author David Turanski
|
||||
* @since 2.0
|
||||
*/
|
||||
public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
|
||||
|
||||
|
||||
private volatile String temporaryFileSuffix =".writing";
|
||||
|
||||
private final SessionFactory<F> sessionFactory;
|
||||
|
||||
|
||||
private volatile boolean autoCreateDirectory = false;
|
||||
|
||||
private volatile boolean useTemporaryFileName = true;
|
||||
|
||||
private volatile ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor;
|
||||
|
||||
|
||||
private volatile ExpressionEvaluatingMessageProcessor<String> temporaryDirectoryExpressionProcessor;
|
||||
|
||||
private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
|
||||
@@ -66,6 +69,8 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
|
||||
|
||||
private volatile String remoteFileSeparator = "/";
|
||||
|
||||
private volatile boolean hasExplicitlySetSuffix;
|
||||
|
||||
|
||||
public FileTransferringMessageHandler(SessionFactory<F> sessionFactory) {
|
||||
Assert.notNull(sessionFactory, "sessionFactory must not be null");
|
||||
@@ -101,6 +106,16 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
|
||||
this.temporaryDirectory = temporaryDirectory;
|
||||
}
|
||||
|
||||
protected boolean isUseTemporaryFileName() {
|
||||
return useTemporaryFileName;
|
||||
}
|
||||
|
||||
|
||||
public void setUseTemporaryFileName(boolean useTemporaryFileName) {
|
||||
this.useTemporaryFileName = useTemporaryFileName;
|
||||
}
|
||||
|
||||
|
||||
public void setFileNameGenerator(FileNameGenerator fileNameGenerator) {
|
||||
this.fileNameGenerator = (fileNameGenerator != null) ? fileNameGenerator : new DefaultFileNameGenerator();
|
||||
}
|
||||
@@ -110,6 +125,8 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
|
||||
}
|
||||
|
||||
public void setTemporaryFileSuffix(String temporaryFileSuffix) {
|
||||
Assert.notNull(temporaryFileSuffix, "'temporaryFileSuffix' must not be null");
|
||||
this.hasExplicitlySetSuffix = true;
|
||||
this.temporaryFileSuffix = temporaryFileSuffix;
|
||||
}
|
||||
|
||||
@@ -118,8 +135,11 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
|
||||
if (this.autoCreateDirectory){
|
||||
Assert.hasText(this.remoteFileSeparator, "'remoteFileSeparator' must not be empty when 'autoCreateDirectory' is set to 'true'");
|
||||
}
|
||||
if (hasExplicitlySetSuffix && !useTemporaryFileName){
|
||||
this.logger.warn("Since 'use-temporary-file-name' is set to 'false' the value of 'temporary-file-suffix' has no effect");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void handleMessageInternal(Message<?> message) throws Exception {
|
||||
File file = this.redeemForStorableFile(message);
|
||||
@@ -197,14 +217,15 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
|
||||
|
||||
private void sendFileToRemoteDirectory(File file, String temporaryRemoteDirectory, String remoteDirectory, String fileName, Session<F> session)
|
||||
throws FileNotFoundException, IOException {
|
||||
|
||||
|
||||
remoteDirectory = this.normalizeDirectoryPath(remoteDirectory);
|
||||
temporaryRemoteDirectory = this.normalizeDirectoryPath(temporaryRemoteDirectory);
|
||||
|
||||
|
||||
String remoteFilePath = remoteDirectory + fileName;
|
||||
String tempRemoteFilePath = temporaryRemoteDirectory + fileName;
|
||||
// write remote file first with .writing extension
|
||||
String tempFilePath = tempRemoteFilePath + this.temporaryFileSuffix;
|
||||
// write remote file first with temporary file extension if enabled
|
||||
|
||||
String tempFilePath = tempRemoteFilePath + (useTemporaryFileName ? this.temporaryFileSuffix : "");
|
||||
|
||||
if (this.autoCreateDirectory) {
|
||||
try {
|
||||
@@ -219,8 +240,10 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
|
||||
FileInputStream fileInputStream = new FileInputStream(file);
|
||||
try {
|
||||
session.write(fileInputStream, tempFilePath);
|
||||
// then rename it to its final name
|
||||
session.rename(tempFilePath, remoteFilePath);
|
||||
// then rename it to its final name if necessary
|
||||
if (useTemporaryFileName){
|
||||
session.rename(tempFilePath, remoteFilePath);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessagingException("Failed to write to '" + tempFilePath + "' while uploading the file", e);
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
|
||||
package org.springframework.integration.file.remote.handler;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -29,13 +36,7 @@ import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import org.springframework.test.annotation.ExpectedException;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
@@ -47,7 +48,7 @@ public class FileTransferringMessageHandlerTests {
|
||||
public <F> void testRemoteDirWithEmptyString() throws Exception{
|
||||
SessionFactory<F> sf = mock(SessionFactory.class);
|
||||
Session<F> session = mock(Session.class);
|
||||
|
||||
|
||||
when(sf.getSession()).thenReturn(session);
|
||||
doAnswer(new Answer<Object>() {
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
@@ -69,7 +70,7 @@ public class FileTransferringMessageHandlerTests {
|
||||
public <F> void testRemoteDirWithNull() throws Exception{
|
||||
SessionFactory<F> sf = mock(SessionFactory.class);
|
||||
Session<F> session = mock(Session.class);
|
||||
|
||||
|
||||
when(sf.getSession()).thenReturn(session);
|
||||
doAnswer(new Answer<Object>() {
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
@@ -87,4 +88,36 @@ public class FileTransferringMessageHandlerTests {
|
||||
verify(session, times(1)).write(Mockito.any(InputStream.class), Mockito.anyString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public <F> void testEmptyTemporaryFileSuffixCannotBeNull() throws Exception {
|
||||
SessionFactory<F> sf = mock(SessionFactory.class);
|
||||
Session<F> session = mock(Session.class);
|
||||
when(sf.getSession()).thenReturn(session);
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
FileTransferringMessageHandler<F> handler = new FileTransferringMessageHandler<F>(sf);
|
||||
handler.setRemoteDirectoryExpression(parser.parseExpression("headers['path']"));
|
||||
handler.setTemporaryFileSuffix(null);
|
||||
handler.onInit();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public <F> void testUseTemporaryFileNameFalse() throws Exception{
|
||||
SessionFactory<F> sf = mock(SessionFactory.class);
|
||||
Session<F> session = mock(Session.class);
|
||||
|
||||
when(sf.getSession()).thenReturn(session);
|
||||
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
FileTransferringMessageHandler<F> handler = new FileTransferringMessageHandler<F>(sf);
|
||||
handler.setRemoteDirectoryExpression(parser.parseExpression("headers['path']"));
|
||||
handler.setUseTemporaryFileName(false);
|
||||
handler.afterPropertiesSet();
|
||||
Message<?> message = MessageBuilder.withPayload("hello").setHeader("path", null).build();
|
||||
handler.handleMessage(message);
|
||||
verify(session, times(1)).write(Mockito.any(InputStream.class), Mockito.anyString());
|
||||
verify(session, times(0)).rename(Mockito.anyString(), Mockito.anyString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -80,6 +80,13 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="use-temporary-file-name" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to suppress using a temporary file name while writing the file.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="order" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -439,6 +446,7 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
||||
<xsd:attribute name="remote-file-separator" type="xsd:string"
|
||||
default="/">
|
||||
<xsd:annotation>
|
||||
|
||||
@@ -45,14 +45,25 @@
|
||||
remote-filename-generator="fileNameGenerator"
|
||||
order="12"/>
|
||||
|
||||
<int-ftp:outbound-channel-adapter id="ftpOutbound3"
|
||||
channel="anotherFtpChannel"
|
||||
session-factory="cachingSessionFactory"
|
||||
remote-directory="foo/bar"
|
||||
charset="UTF-8"
|
||||
remote-file-separator="."
|
||||
use-temporary-file-name="false"
|
||||
remote-filename-generator="fileNameGenerator"/>
|
||||
|
||||
|
||||
<int-ftp:outbound-channel-adapter id="simpleAdapter"
|
||||
channel="ftpChannel"
|
||||
session-factory="cachingSessionFactory"
|
||||
remote-directory="foo/bar"/>
|
||||
|
||||
<int:channel id="anotherFtpChannel"/>
|
||||
|
||||
<int:publish-subscribe-channel id="ftpChannel"/>
|
||||
|
||||
|
||||
<bean id="fileNameGenerator" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.FileNameGenerator"/>
|
||||
</bean>
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.integration.ftp.config;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import java.util.Iterator;
|
||||
@@ -96,4 +97,14 @@ public class FtpOutboundChannelAdapterParserTests {
|
||||
assertEquals(DefaultFtpSessionFactory.class, innerSfProperty.getClass());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTemporaryFileSuffix() {
|
||||
ApplicationContext ac =
|
||||
new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-context.xml", this.getClass());
|
||||
FileTransferringMessageHandler<?> handler =
|
||||
(FileTransferringMessageHandler<?>)TestUtils.getPropertyValue(ac.getBean("ftpOutbound3"), "handler");
|
||||
assertFalse((Boolean)TestUtils.getPropertyValue(handler,"useTemporaryFileName"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -83,6 +83,13 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="use-temporary-file-name" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to suppress using a temporary file name while writing the file.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="order" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
<property name="port" value="2222"/>
|
||||
<property name="user" value="oleg"/>
|
||||
</bean>
|
||||
|
||||
|
||||
<int:publish-subscribe-channel id="inputChannel"/>
|
||||
|
||||
|
||||
<int-sftp:outbound-channel-adapter id="sftpOutboundAdapter"
|
||||
session-factory="sftpSessionFactory"
|
||||
channel="inputChannel"
|
||||
@@ -29,7 +29,7 @@
|
||||
remote-directory="foo/bar"
|
||||
temporary-remote-directory="foo/baz"
|
||||
order="23"/>
|
||||
|
||||
|
||||
<int-sftp:outbound-channel-adapter id="sftpOutboundAdapterWithExpression"
|
||||
session-factory="sftpSessionFactory"
|
||||
channel="inputChannel"
|
||||
@@ -43,6 +43,13 @@
|
||||
channel="inputChannel"
|
||||
charset="UTF-8"
|
||||
remote-directory="foo/bar"/>
|
||||
|
||||
<int-sftp:outbound-channel-adapter id="sftpOutboundAdapterWithNoTemporaryFileName"
|
||||
session-factory="sftpSessionFactory"
|
||||
channel="inputChannel"
|
||||
charset="UTF-8"
|
||||
use-temporary-file-name="false"
|
||||
remote-directory="foo/bar"/>
|
||||
|
||||
<bean id="fileNameGenerator" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.FileNameGenerator"/>
|
||||
|
||||
@@ -20,6 +20,7 @@ import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
@@ -46,6 +47,7 @@ import org.springframework.integration.test.util.TestUtils;
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
*/
|
||||
public class OutboundChannelAdapterParserTests {
|
||||
|
||||
@@ -85,7 +87,7 @@ public class OutboundChannelAdapterParserTests {
|
||||
assertSame(TestUtils.getPropertyValue(context.getBean("sftpOutboundAdapterWithExpression"), "handler"), iterator.next());
|
||||
assertSame(handler, iterator.next());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOutboundChannelAdapterWithWithRemoteDirectoryAndFileExpression(){
|
||||
ApplicationContext context =
|
||||
@@ -107,6 +109,15 @@ public class OutboundChannelAdapterParserTests {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOutboundChannelAdapterWithNoTemporaryFileName(){
|
||||
ApplicationContext context =
|
||||
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context.xml", this.getClass());
|
||||
Object consumer = context.getBean("sftpOutboundAdapterWithNoTemporaryFileName");
|
||||
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
|
||||
assertFalse((Boolean)TestUtils.getPropertyValue(handler,"useTemporaryFileName"));
|
||||
}
|
||||
|
||||
@Test(expected=BeanDefinitionStoreException.class)
|
||||
public void testFailWithRemoteDirAndExpression(){
|
||||
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail.xml", this.getClass());
|
||||
|
||||
Reference in New Issue
Block a user