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);
|
||||
|
||||
Reference in New Issue
Block a user