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:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user