Merge pull request #388 from olegz/INT-2489

This commit is contained in:
Gary Russell
2012-03-27 14:10:27 -04:00
9 changed files with 140 additions and 26 deletions

View File

@@ -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");

View File

@@ -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);

View File

@@ -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());
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"));
}
}

View File

@@ -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>

View File

@@ -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"/>

View File

@@ -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());