diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java index 643790e4a9..7ac68143e0 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java @@ -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"); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java index d642630207..14d3ef7f2a 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java @@ -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 extends AbstractMessageHandler { - + private volatile String temporaryFileSuffix =".writing"; private final SessionFactory sessionFactory; - + private volatile boolean autoCreateDirectory = false; + private volatile boolean useTemporaryFileName = true; + private volatile ExpressionEvaluatingMessageProcessor directoryExpressionProcessor; - + private volatile ExpressionEvaluatingMessageProcessor temporaryDirectoryExpressionProcessor; private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator(); @@ -66,6 +69,8 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { private volatile String remoteFileSeparator = "/"; + private volatile boolean hasExplicitlySetSuffix; + public FileTransferringMessageHandler(SessionFactory sessionFactory) { Assert.notNull(sessionFactory, "sessionFactory must not be null"); @@ -101,6 +106,16 @@ public class FileTransferringMessageHandler 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 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 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 extends AbstractMessageHandler { private void sendFileToRemoteDirectory(File file, String temporaryRemoteDirectory, String remoteDirectory, String fileName, Session 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 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); diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandlerTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandlerTests.java index f0b64968a7..7337026b63 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandlerTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandlerTests.java @@ -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 void testRemoteDirWithEmptyString() throws Exception{ SessionFactory sf = mock(SessionFactory.class); Session session = mock(Session.class); - + when(sf.getSession()).thenReturn(session); doAnswer(new Answer() { public Object answer(InvocationOnMock invocation) throws Throwable { @@ -69,7 +70,7 @@ public class FileTransferringMessageHandlerTests { public void testRemoteDirWithNull() throws Exception{ SessionFactory sf = mock(SessionFactory.class); Session session = mock(Session.class); - + when(sf.getSession()).thenReturn(session); doAnswer(new Answer() { 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 void testEmptyTemporaryFileSuffixCannotBeNull() throws Exception { + SessionFactory sf = mock(SessionFactory.class); + Session session = mock(Session.class); + when(sf.getSession()).thenReturn(session); + ExpressionParser parser = new SpelExpressionParser(); + FileTransferringMessageHandler handler = new FileTransferringMessageHandler(sf); + handler.setRemoteDirectoryExpression(parser.parseExpression("headers['path']")); + handler.setTemporaryFileSuffix(null); + handler.onInit(); + } + + @SuppressWarnings("unchecked") + @Test + public void testUseTemporaryFileNameFalse() throws Exception{ + SessionFactory sf = mock(SessionFactory.class); + Session session = mock(Session.class); + + when(sf.getSession()).thenReturn(session); + + ExpressionParser parser = new SpelExpressionParser(); + FileTransferringMessageHandler handler = new FileTransferringMessageHandler(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()); + } + } diff --git a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd index cc428587de..73e4c2fe59 100644 --- a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd +++ b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd @@ -80,6 +80,13 @@ + + + + Allows you to suppress using a temporary file name while writing the file. + + + @@ -439,6 +446,7 @@ + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml index 3d3c19e61c..2adbd5ada3 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml @@ -45,14 +45,25 @@ remote-filename-generator="fileNameGenerator" order="12"/> + + + + - + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java index 1fb8b57f00..19bb57c581 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java @@ -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")); + } + } diff --git a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd index 7ac343233f..093c328604 100644 --- a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd +++ b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd @@ -83,6 +83,13 @@ + + + + Allows you to suppress using a temporary file name while writing the file. + + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml index 54b467ba08..f0ce6448e3 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml @@ -16,9 +16,9 @@ - + - + - + + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests.java index a20917882a..d33d3302a6 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests.java @@ -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());