GH-2996: Add Resource for remote file transfer (#3231)

* GH-2996: Add Resource for remote file transfer

Fixes https://github.com/spring-projects/spring-integration/issues/2996

* Fix `RemoteFileTemplate.send()` to support a `Resource` payload
for remote file transferring content
* Code style clean up for `RemoteFileTemplate`
* Remove `volatile` for configuration properties for better performance
* Change a `charset` to the `Charset` for only once conversion from string
during configuration phase
* Fix (S)FTP tests for new functionality
* Change affected tests to JUnit 5
* Document a new feature; mention all the supported types and `FileExistsMode` constants

* * Fix language in `whats-new.adoc`
This commit is contained in:
Artem Bilan
2020-03-31 16:09:07 -04:00
committed by GitHub
parent 63f8907e88
commit 6d5690fd58
8 changed files with 171 additions and 122 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -23,6 +23,8 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
@@ -32,6 +34,7 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.expression.Expression;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
@@ -65,7 +68,7 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
private final Log logger = LogFactory.getLog(this.getClass());
/**
* the {@link SessionFactory} for acquiring remote file Sessions.
* The {@link SessionFactory} for acquiring remote file Sessions.
*/
protected final SessionFactory<F> sessionFactory; // NOSONAR
@@ -76,29 +79,29 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
private final AtomicInteger activeTemplateCallbacks = new AtomicInteger();
private volatile String temporaryFileSuffix = ".writing";
private String temporaryFileSuffix = ".writing";
private volatile boolean autoCreateDirectory = false;
private boolean autoCreateDirectory = false;
private volatile boolean useTemporaryFileName = true;
private boolean useTemporaryFileName = true;
private volatile ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor;
private ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor;
private volatile ExpressionEvaluatingMessageProcessor<String> temporaryDirectoryExpressionProcessor;
private ExpressionEvaluatingMessageProcessor<String> temporaryDirectoryExpressionProcessor;
private volatile ExpressionEvaluatingMessageProcessor<String> fileNameProcessor;
private ExpressionEvaluatingMessageProcessor<String> fileNameProcessor;
private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
private FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
private volatile boolean fileNameGeneratorSet;
private boolean fileNameGeneratorSet;
private volatile String charset = "UTF-8";
private Charset charset = StandardCharsets.UTF_8;
private volatile String remoteFileSeparator = "/";
private String remoteFileSeparator = "/";
private volatile boolean hasExplicitlySetSuffix;
private boolean hasExplicitlySetSuffix;
private volatile BeanFactory beanFactory;
private BeanFactory beanFactory;
/**
* Construct a {@link RemoteFileTemplate} with the supplied session factory.
@@ -216,7 +219,7 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
* @param charset the charset.
*/
public void setCharset(String charset) {
this.charset = charset;
this.charset = Charset.forName(charset);
}
/**
@@ -264,12 +267,12 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
}
@Override
public String append(final Message<?> message) {
public String append(Message<?> message) {
return append(message, null);
}
@Override
public String append(final Message<?> message, String subDirectory) {
public String append(Message<?> message, String subDirectory) {
return send(message, subDirectory, FileExistsMode.APPEND);
}
@@ -279,14 +282,14 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
}
@Override
public String send(final Message<?> message, String subDirectory, FileExistsMode... mode) {
public String send(Message<?> message, String subDirectory, FileExistsMode... mode) {
FileExistsMode modeToUse = mode == null || mode.length < 1 || mode[0] == null
? FileExistsMode.REPLACE
: mode[0];
return send(message, subDirectory, modeToUse);
}
private String send(final Message<?> message, final String subDirectory, final FileExistsMode mode) {
private String send(Message<?> message, String subDirectory, FileExistsMode mode) {
Assert.notNull(this.directoryExpressionProcessor, "'remoteDirectoryExpression' is required");
Assert.isTrue(!FileExistsMode.APPEND.equals(mode) || !this.useTemporaryFileName,
"Cannot append when using a temporary file name");
@@ -354,17 +357,17 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
}
@Override
public boolean exists(final String path) {
public boolean exists(String path) {
return execute(session -> session.exists(path));
}
@Override
public boolean remove(final String path) {
public boolean remove(String path) {
return execute(session -> session.remove(path));
}
@Override
public void rename(final String fromPath, final String toPath) {
public void rename(String fromPath, String toPath) {
Assert.hasText(fromPath, "Old filename cannot be null or empty");
Assert.hasText(toPath, "New filename cannot be null or empty");
@@ -390,7 +393,7 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
}
@Override
public boolean get(final String remotePath, final InputStreamCallback callback) {
public boolean get(String remotePath, InputStreamCallback callback) {
Assert.notNull(remotePath, "'remotePath' cannot be null");
return execute(session -> {
try (InputStream inputStream = session.readRaw(remotePath)) {
@@ -419,7 +422,6 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
return this.sessionFactory.getSession();
}
@SuppressWarnings("rawtypes")
@Override
public <T> T execute(SessionCallback<F, T> callback) {
Session<F> session = null;
@@ -440,7 +442,7 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
if (session != null) {
session.dirty();
}
if (e instanceof MessagingException) {
if (e instanceof MessagingException) { // NOSONAR
throw (MessagingException) e;
}
throw new MessagingException("Failed to execute on session", e);
@@ -498,7 +500,7 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
}
}
else if (payload instanceof byte[] || payload instanceof String) {
byte[] bytes = null;
byte[] bytes;
if (payload instanceof String) {
bytes = ((String) payload).getBytes(this.charset);
name = "String payload";
@@ -513,6 +515,12 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
dataInputStream = (InputStream) payload;
name = "InputStream payload";
}
else if (payload instanceof Resource) {
Resource resource = (Resource) payload;
dataInputStream = resource.getInputStream();
String filename = resource.getFilename();
name = filename != null ? filename : "Resource payload";
}
else {
throw new IllegalArgumentException("Unsupported payload type ["
+ payload.getClass().getName()
@@ -563,6 +571,7 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
private void doSend(Session<F> session, FileExistsMode mode, String remoteFilePath, String tempFilePath,
InputStream stream) throws IOException {
boolean rename = this.useTemporaryFileName;
if (FileExistsMode.REPLACE.equals(mode)) {
session.write(stream, tempFilePath);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 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.
@@ -32,14 +32,16 @@ import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
@@ -61,13 +63,13 @@ public class RemoteFileTemplateTests {
private Session<Object> session;
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@TempDir
Path folder;
private File file;
@SuppressWarnings("unchecked")
@Before
@BeforeEach
public void setUp() throws Exception {
SessionFactory<Object> sessionFactory = mock(SessionFactory.class);
this.template = new RemoteFileTemplate<>(sessionFactory);
@@ -76,7 +78,7 @@ public class RemoteFileTemplateTests {
this.template.afterPropertiesSet();
this.session = mock(Session.class);
when(sessionFactory.getSession()).thenReturn(this.session);
this.file = this.folder.newFile();
this.file = Files.createTempFile(this.folder, null, null).toFile();
}
@Test
@@ -146,6 +148,12 @@ public class RemoteFileTemplateTests {
verify(this.session).write(any(InputStream.class), any());
}
@Test
public void testResource() throws IOException {
this.template.send(new GenericMessage<>(new ByteArrayResource("foo".getBytes())), FileExistsMode.IGNORE);
verify(this.session).write(any(InputStream.class), any());
}
@Test
public void testMissingFile() {
this.template.send(new GenericMessage<>(new File(UUID.randomUUID().toString())), FileExistsMode.IGNORE);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -17,12 +17,13 @@
package org.springframework.integration.ftp.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,18 +46,17 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 2.0
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringJUnitConfig
@DirtiesContext
public class FtpOutboundChannelAdapterParserTests {
@@ -87,7 +87,7 @@ public class FtpOutboundChannelAdapterParserTests {
private FileNameGenerator fileNameGenerator;
@Test
public void testFtpOutboundChannelAdapterComplete() throws Exception {
public void testFtpOutboundChannelAdapterComplete() {
assertThat(TestUtils.getPropertyValue(ftpOutbound, "inputChannel")).isEqualTo(ftpChannel);
assertThat(ftpOutbound.getComponentName()).isEqualTo("ftpOutbound");
FileTransferringMessageHandler<?> handler =
@@ -100,7 +100,7 @@ public class FtpOutboundChannelAdapterParserTests {
assertThat(remoteFileSeparator).isEqualTo("");
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator"))
.isEqualTo(this.fileNameGenerator);
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo("UTF-8");
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo(StandardCharsets.UTF_8);
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.directoryExpressionProcessor")).isNotNull();
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryDirectoryExpressionProcessor"))
.isNotNull();
@@ -122,9 +122,12 @@ public class FtpOutboundChannelAdapterParserTests {
assertThat(TestUtils.getPropertyValue(ftpOutbound, "handler.mode")).isEqualTo(FileExistsMode.APPEND);
}
@Test(expected = BeanCreationException.class)
public void testFailWithEmptyRfsAndAcdTrue() throws Exception {
new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-fail.xml", this.getClass()).close();
@Test
public void testFailWithEmptyRfsAndAcdTrue() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() ->
new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-fail.xml",
getClass()));
}
@Test
@@ -139,7 +142,7 @@ public class FtpOutboundChannelAdapterParserTests {
@Test
public void adviceChain() {
MessageHandler handler = TestUtils.getPropertyValue(advisedAdapter, "handler", MessageHandler.class);
handler.handleMessage(new GenericMessage<String>("foo"));
handler.handleMessage(new GenericMessage<>("foo"));
assertThat(adviceCalled).isEqualTo(1);
}
@@ -152,7 +155,7 @@ public class FtpOutboundChannelAdapterParserTests {
}
@Test
public void testBeanExpressions() throws Exception {
public void testBeanExpressions() {
FileTransferringMessageHandler<?> handler =
TestUtils.getPropertyValue(withBeanExpressions, "handler", FileTransferringMessageHandler.class);
ExpressionEvaluatingMessageProcessor<?> dirExpProc = TestUtils.getPropertyValue(handler,

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -18,8 +18,9 @@ package org.springframework.integration.ftp.config;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.endpoint.EventDrivenConsumer;
@@ -29,17 +30,17 @@ import org.springframework.integration.ftp.session.DefaultFtpsSessionFactory;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Gary Russell
* @author Artem Bilan
*
* @since 2.0
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringJUnitConfig
@DirtiesContext
public class FtpsOutboundChannelAdapterParserTests {
@@ -53,15 +54,18 @@ public class FtpsOutboundChannelAdapterParserTests {
private FileNameGenerator fileNameGenerator;
@Test
public void testFtpsOutboundChannelAdapterComplete() throws Exception {
assertThat(ftpOutbound instanceof EventDrivenConsumer).isTrue();
public void testFtpsOutboundChannelAdapterComplete() {
assertThat(ftpOutbound).isInstanceOf(EventDrivenConsumer.class);
assertThat(TestUtils.getPropertyValue(ftpOutbound, "inputChannel")).isEqualTo(this.ftpChannel);
assertThat(ftpOutbound.getComponentName()).isEqualTo("ftpOutbound");
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(ftpOutbound, "handler", FileTransferringMessageHandler.class);
FileTransferringMessageHandler<?> handler =
TestUtils.getPropertyValue(ftpOutbound, "handler", FileTransferringMessageHandler.class);
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator"))
.isEqualTo(this.fileNameGenerator);
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo("UTF-8");
DefaultFtpsSessionFactory sf = TestUtils.getPropertyValue(handler, "remoteFileTemplate.sessionFactory", DefaultFtpsSessionFactory.class);
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo(StandardCharsets.UTF_8);
DefaultFtpsSessionFactory sf =
TestUtils.getPropertyValue(handler, "remoteFileTemplate.sessionFactory",
DefaultFtpsSessionFactory.class);
assertThat(TestUtils.getPropertyValue(sf, "host")).isEqualTo("localhost");
assertThat(TestUtils.getPropertyValue(sf, "port")).isEqualTo(22);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -17,15 +17,17 @@
package org.springframework.integration.sftp.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
@@ -41,47 +43,54 @@ import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Oleg Zhurakousky
* @author Gary Russell
* @author David Turanski
* @author Gunnar Hillert
* @author Artem Bilan
*/
@SpringJUnitConfig
@DirtiesContext
public class OutboundChannelAdapterParserTests {
private static volatile int adviceCalled;
@Autowired
ApplicationContext context;
@Test
public void testOutboundChannelAdapterWithId() {
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context.xml", this.getClass());
Object consumer = context.getBean("sftpOutboundAdapter");
assertThat(consumer instanceof EventDrivenConsumer).isTrue();
PublishSubscribeChannel channel = context.getBean("inputChannel", PublishSubscribeChannel.class);
EventDrivenConsumer consumer = this.context.getBean("sftpOutboundAdapter", EventDrivenConsumer.class);
PublishSubscribeChannel channel = this.context.getBean("inputChannel", PublishSubscribeChannel.class);
assertThat(TestUtils.getPropertyValue(consumer, "inputChannel")).isEqualTo(channel);
assertThat(((EventDrivenConsumer) consumer).getComponentName()).isEqualTo("sftpOutboundAdapter");
assertThat(consumer.getComponentName()).isEqualTo("sftpOutboundAdapter");
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(consumer, "handler",
FileTransferringMessageHandler.class);
String remoteFileSeparator = (String) TestUtils.getPropertyValue(handler,
"remoteFileTemplate.remoteFileSeparator");
String remoteFileSeparator =
TestUtils.getPropertyValue(handler, "remoteFileTemplate.remoteFileSeparator", String.class);
assertThat(remoteFileSeparator).isNotNull();
assertThat(remoteFileSeparator).isEqualTo(".");
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryFileSuffix", String.class))
.isEqualTo(".bar");
Expression remoteDirectoryExpression = (Expression) TestUtils.getPropertyValue(handler,
"remoteFileTemplate.directoryExpressionProcessor.expression");
assertThat(remoteDirectoryExpression).isNotNull();
assertThat(remoteDirectoryExpression instanceof LiteralExpression).isTrue();
Expression remoteDirectoryExpression =
TestUtils.getPropertyValue(handler, "remoteFileTemplate.directoryExpressionProcessor.expression",
Expression.class);
assertThat(remoteDirectoryExpression)
.isNotNull()
.isInstanceOf(LiteralExpression.class);
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryDirectoryExpressionProcessor"))
.isNotNull();
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator"))
.isEqualTo(context.getBean("fileNameGenerator"));
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo("UTF-8");
CachingSessionFactory<?> sessionFactory = TestUtils.getPropertyValue(handler,
"remoteFileTemplate.sessionFactory", CachingSessionFactory.class);
DefaultSftpSessionFactory clientFactory = TestUtils.getPropertyValue(sessionFactory, "sessionFactory",
DefaultSftpSessionFactory.class);
.isEqualTo(this.context.getBean("fileNameGenerator"));
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo(StandardCharsets.UTF_8);
CachingSessionFactory<?> sessionFactory =
TestUtils.getPropertyValue(handler, "remoteFileTemplate.sessionFactory", CachingSessionFactory.class);
DefaultSftpSessionFactory clientFactory =
TestUtils.getPropertyValue(sessionFactory, "sessionFactory", DefaultSftpSessionFactory.class);
assertThat(TestUtils.getPropertyValue(clientFactory, "host")).isEqualTo("localhost");
assertThat(TestUtils.getPropertyValue(clientFactory, "port")).isEqualTo(2222);
assertThat(TestUtils.getPropertyValue(handler, "order")).isEqualTo(23);
@@ -93,73 +102,68 @@ public class OutboundChannelAdapterParserTests {
"handlers");
Iterator<MessageHandler> iterator = handlers.iterator();
assertThat(iterator.next())
.isSameAs(TestUtils.getPropertyValue(context.getBean("sftpOutboundAdapterWithExpression"), "handler"));
.isSameAs(TestUtils.getPropertyValue(
this.context.getBean("sftpOutboundAdapterWithExpression"), "handler"));
assertThat(iterator.next()).isSameAs(handler);
assertThat(TestUtils.getPropertyValue(handler, "chmod")).isEqualTo(384);
context.close();
}
@Test
public void testOutboundChannelAdapterWithWithRemoteDirectoryAndFileExpression() {
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context.xml", this.getClass());
Object consumer = context.getBean("sftpOutboundAdapterWithExpression");
assertThat(consumer instanceof EventDrivenConsumer).isTrue();
assertThat(TestUtils.getPropertyValue(consumer, "inputChannel")).isEqualTo(context.getBean("inputChannel"));
assertThat(((EventDrivenConsumer) consumer).getComponentName()).isEqualTo("sftpOutboundAdapterWithExpression");
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
SpelExpression remoteDirectoryExpression = (SpelExpression) TestUtils.getPropertyValue(handler,
"remoteFileTemplate.directoryExpressionProcessor.expression");
EventDrivenConsumer consumer =
this.context.getBean("sftpOutboundAdapterWithExpression", EventDrivenConsumer.class);
assertThat(TestUtils.getPropertyValue(consumer, "inputChannel"))
.isEqualTo(this.context.getBean("inputChannel"));
assertThat(consumer.getComponentName()).isEqualTo("sftpOutboundAdapterWithExpression");
FileTransferringMessageHandler<?> handler = TestUtils
.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
SpelExpression remoteDirectoryExpression =
TestUtils.getPropertyValue(handler, "remoteFileTemplate.directoryExpressionProcessor.expression",
SpelExpression.class);
assertThat(remoteDirectoryExpression).isNotNull();
assertThat(remoteDirectoryExpression.getExpressionString()).isEqualTo("'foo' + '/' + 'bar'");
FileNameGenerator generator = (FileNameGenerator) TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator");
FileNameGenerator generator =
TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator", FileNameGenerator.class);
Expression fileNameGeneratorExpression = TestUtils.getPropertyValue(generator, "expression", Expression.class);
assertThat(fileNameGeneratorExpression).isNotNull();
assertThat(fileNameGeneratorExpression.getExpressionString()).isEqualTo("payload.getName() + '-foo'");
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo("UTF-8");
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo(StandardCharsets.UTF_8);
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryDirectoryExpressionProcessor"))
.isNull();
context.close();
}
@Test
public void testOutboundChannelAdapterWithNoTemporaryFileName() {
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context.xml", this.getClass());
Object consumer = context.getBean("sftpOutboundAdapterWithNoTemporaryFileName");
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
Object consumer = this.context.getBean("sftpOutboundAdapterWithNoTemporaryFileName");
FileTransferringMessageHandler<?> handler = TestUtils
.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
assertThat((Boolean) TestUtils.getPropertyValue(handler, "remoteFileTemplate.useTemporaryFileName")).isFalse();
context.close();
}
@Test
public void advised() {
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context.xml", this.getClass());
Object consumer = context.getBean("advised");
Object consumer = this.context.getBean("advised");
MessageHandler handler = TestUtils.getPropertyValue(consumer, "handler", MessageHandler.class);
handler.handleMessage(new GenericMessage<String>("foo"));
handler.handleMessage(new GenericMessage<>("foo"));
assertThat(adviceCalled).isEqualTo(1);
context.close();
}
@Test
public void testFailWithRemoteDirAndExpression() {
try {
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail.xml", this.getClass())
.close();
fail("Exception expected");
}
catch (BeanDefinitionStoreException e) {
assertThat(e.getMessage()).contains("Only one of 'remote-directory'");
}
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() ->
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail.xml",
getClass()))
.withMessageContaining("Only one of 'remote-directory'");
}
@Test(expected = BeanDefinitionStoreException.class)
@Test
public void testFailWithFileExpressionAndFileGenerator() {
new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail-fileFileGen.xml",
this.getClass()).close();
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() ->
new ClassPathXmlApplicationContext(
"OutboundChannelAdapterParserTests-context-fail-fileFileGen.xml",
getClass()));
}
public static class FooAdvice extends AbstractRequestHandlerAdvice {
@@ -171,4 +175,5 @@ public class OutboundChannelAdapterParserTests {
}
}
}

View File

@@ -821,7 +821,9 @@ The FTP outbound channel adapter supports the following payloads:
* `java.io.File`: The actual file object
* `byte[]`: A byte array that represents the file contents
* `java.lang.String`: Text that represents the file contents.
* `java.lang.String`: Text that represents the file contents
* `java.io.InputStream`: a stream of data to transfer to remote file
* `org.springframework.core.io.Resource`: a resource for data to transfer to remote file
The following example shows how to configure an `outbound-channel-adapter`:
@@ -859,7 +861,9 @@ By default, an existing file is overwritten.
The modes are defined by the `FileExistsMode` enumeration, which includes the following values:
* `REPLACE` (default)
*`APPEND`
* `REPLACE_IF_MODIFIED`
* `APPEND`
* `APPEND_NO_FLUSH`
* `IGNORE`
* `FAIL`

View File

@@ -822,6 +822,8 @@ Similar to the FTP outbound adapter, the SFTP outbound channel adapter supports
* `java.io.File`: The actual file object
* `byte[]`: A byte array that represents the file contents
* `java.lang.String`: Text that represents the file contents
* `java.io.InputStream`: a stream of data to transfer to remote file
* `org.springframework.core.io.Resource`: a resource for data to transfer to remote file
The following example shows how to configure an SFTP outbound channel adapter:
@@ -852,7 +854,15 @@ In the preceding example, we define the `remote-filename-generator-expression` a
Starting with version 4.1, you can specify the `mode` when you transferring the file.
By default, an existing file is overwritten.
The modes are defined by the `FileExistsMode` enumeration, which has the following values: `REPLACE` (default), `APPEND`, `IGNORE`, and `FAIL`.
The modes are defined by the `FileExistsMode` enumeration, which includes the following values:
* `REPLACE` (default)
* `REPLACE_IF_MODIFIED`
* `APPEND`
* `APPEND_NO_FLUSH`
* `IGNORE`
* `FAIL`
With `IGNORE` and `FAIL`, the file is not transferred.
`FAIL` causes an exception to be thrown, while `IGNORE` silently ignores the transfer (although a `DEBUG` log entry is produced).

View File

@@ -126,3 +126,9 @@ See <<./mqtt.adoc#mqtt-ack-mode,Manual Acks>> for more information.
The outbound adapter now publishes a `MqttConnectionFailedEvent` when a connection can't be created, or is lost.
Previously, only the inbound adapter did so.
See <<./mqtt.adoc#mqtt-events,MQTT Events>>.
[[x5.3-sftp]]
=== (S)FTP Changes
The `FileTransferringMessageHandler` (for FTP and SFTP, for example) in addition to `File`, `byte[]`, `String` and `InputStream` now also supports an `org.springframework.core.io.Resource`.
See <<./sftp.adoc#sftp,SFTP Support>> and <<./ftp.adoc#ftp,FTP Support>> for more information.