Skip to content

Commit 6d5690f

Browse files
authored
GH-2996: Add Resource for remote file transfer (#3231)
* GH-2996: Add Resource for remote file transfer Fixes #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`
1 parent 63f8907 commit 6d5690f

File tree

8 files changed

+171
-122
lines changed

8 files changed

+171
-122
lines changed

spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2019 the original author or authors.
2+
* Copyright 2013-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,8 @@
2323
import java.io.FileNotFoundException;
2424
import java.io.IOException;
2525
import java.io.InputStream;
26+
import java.nio.charset.Charset;
27+
import java.nio.charset.StandardCharsets;
2628
import java.util.concurrent.atomic.AtomicInteger;
2729

2830
import org.apache.commons.logging.Log;
@@ -32,6 +34,7 @@
3234
import org.springframework.beans.factory.BeanFactory;
3335
import org.springframework.beans.factory.BeanFactoryAware;
3436
import org.springframework.beans.factory.InitializingBean;
37+
import org.springframework.core.io.Resource;
3538
import org.springframework.expression.Expression;
3639
import org.springframework.integration.file.DefaultFileNameGenerator;
3740
import org.springframework.integration.file.FileNameGenerator;
@@ -65,7 +68,7 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
6568
private final Log logger = LogFactory.getLog(this.getClass());
6669

6770
/**
68-
* the {@link SessionFactory} for acquiring remote file Sessions.
71+
* The {@link SessionFactory} for acquiring remote file Sessions.
6972
*/
7073
protected final SessionFactory<F> sessionFactory; // NOSONAR
7174

@@ -76,29 +79,29 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
7679

7780
private final AtomicInteger activeTemplateCallbacks = new AtomicInteger();
7881

79-
private volatile String temporaryFileSuffix = ".writing";
82+
private String temporaryFileSuffix = ".writing";
8083

81-
private volatile boolean autoCreateDirectory = false;
84+
private boolean autoCreateDirectory = false;
8285

83-
private volatile boolean useTemporaryFileName = true;
86+
private boolean useTemporaryFileName = true;
8487

85-
private volatile ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor;
88+
private ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor;
8689

87-
private volatile ExpressionEvaluatingMessageProcessor<String> temporaryDirectoryExpressionProcessor;
90+
private ExpressionEvaluatingMessageProcessor<String> temporaryDirectoryExpressionProcessor;
8891

89-
private volatile ExpressionEvaluatingMessageProcessor<String> fileNameProcessor;
92+
private ExpressionEvaluatingMessageProcessor<String> fileNameProcessor;
9093

91-
private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
94+
private FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
9295

93-
private volatile boolean fileNameGeneratorSet;
96+
private boolean fileNameGeneratorSet;
9497

95-
private volatile String charset = "UTF-8";
98+
private Charset charset = StandardCharsets.UTF_8;
9699

97-
private volatile String remoteFileSeparator = "/";
100+
private String remoteFileSeparator = "/";
98101

99-
private volatile boolean hasExplicitlySetSuffix;
102+
private boolean hasExplicitlySetSuffix;
100103

101-
private volatile BeanFactory beanFactory;
104+
private BeanFactory beanFactory;
102105

103106
/**
104107
* Construct a {@link RemoteFileTemplate} with the supplied session factory.
@@ -216,7 +219,7 @@ public void setFileNameGenerator(FileNameGenerator fileNameGenerator) {
216219
* @param charset the charset.
217220
*/
218221
public void setCharset(String charset) {
219-
this.charset = charset;
222+
this.charset = Charset.forName(charset);
220223
}
221224

222225
/**
@@ -264,12 +267,12 @@ public void afterPropertiesSet() {
264267
}
265268

266269
@Override
267-
public String append(final Message<?> message) {
270+
public String append(Message<?> message) {
268271
return append(message, null);
269272
}
270273

271274
@Override
272-
public String append(final Message<?> message, String subDirectory) {
275+
public String append(Message<?> message, String subDirectory) {
273276
return send(message, subDirectory, FileExistsMode.APPEND);
274277
}
275278

@@ -279,14 +282,14 @@ public String send(Message<?> message, FileExistsMode... mode) {
279282
}
280283

281284
@Override
282-
public String send(final Message<?> message, String subDirectory, FileExistsMode... mode) {
285+
public String send(Message<?> message, String subDirectory, FileExistsMode... mode) {
283286
FileExistsMode modeToUse = mode == null || mode.length < 1 || mode[0] == null
284287
? FileExistsMode.REPLACE
285288
: mode[0];
286289
return send(message, subDirectory, modeToUse);
287290
}
288291

289-
private String send(final Message<?> message, final String subDirectory, final FileExistsMode mode) {
292+
private String send(Message<?> message, String subDirectory, FileExistsMode mode) {
290293
Assert.notNull(this.directoryExpressionProcessor, "'remoteDirectoryExpression' is required");
291294
Assert.isTrue(!FileExistsMode.APPEND.equals(mode) || !this.useTemporaryFileName,
292295
"Cannot append when using a temporary file name");
@@ -354,17 +357,17 @@ private String doSend(Message<?> message, String subDirectory, FileExistsMode mo
354357
}
355358

356359
@Override
357-
public boolean exists(final String path) {
360+
public boolean exists(String path) {
358361
return execute(session -> session.exists(path));
359362
}
360363

361364
@Override
362-
public boolean remove(final String path) {
365+
public boolean remove(String path) {
363366
return execute(session -> session.remove(path));
364367
}
365368

366369
@Override
367-
public void rename(final String fromPath, final String toPath) {
370+
public void rename(String fromPath, String toPath) {
368371
Assert.hasText(fromPath, "Old filename cannot be null or empty");
369372
Assert.hasText(toPath, "New filename cannot be null or empty");
370373

@@ -390,7 +393,7 @@ public boolean get(Message<?> message, InputStreamCallback callback) {
390393
}
391394

392395
@Override
393-
public boolean get(final String remotePath, final InputStreamCallback callback) {
396+
public boolean get(String remotePath, InputStreamCallback callback) {
394397
Assert.notNull(remotePath, "'remotePath' cannot be null");
395398
return execute(session -> {
396399
try (InputStream inputStream = session.readRaw(remotePath)) {
@@ -419,7 +422,6 @@ public Session<F> getSession() {
419422
return this.sessionFactory.getSession();
420423
}
421424

422-
@SuppressWarnings("rawtypes")
423425
@Override
424426
public <T> T execute(SessionCallback<F, T> callback) {
425427
Session<F> session = null;
@@ -440,7 +442,7 @@ public <T> T execute(SessionCallback<F, T> callback) {
440442
if (session != null) {
441443
session.dirty();
442444
}
443-
if (e instanceof MessagingException) {
445+
if (e instanceof MessagingException) { // NOSONAR
444446
throw (MessagingException) e;
445447
}
446448
throw new MessagingException("Failed to execute on session", e);
@@ -498,7 +500,7 @@ private StreamHolder payloadToInputStream(Message<?> message) throws MessageDeli
498500
}
499501
}
500502
else if (payload instanceof byte[] || payload instanceof String) {
501-
byte[] bytes = null;
503+
byte[] bytes;
502504
if (payload instanceof String) {
503505
bytes = ((String) payload).getBytes(this.charset);
504506
name = "String payload";
@@ -513,6 +515,12 @@ else if (payload instanceof InputStream) {
513515
dataInputStream = (InputStream) payload;
514516
name = "InputStream payload";
515517
}
518+
else if (payload instanceof Resource) {
519+
Resource resource = (Resource) payload;
520+
dataInputStream = resource.getInputStream();
521+
String filename = resource.getFilename();
522+
name = filename != null ? filename : "Resource payload";
523+
}
516524
else {
517525
throw new IllegalArgumentException("Unsupported payload type ["
518526
+ payload.getClass().getName()
@@ -563,6 +571,7 @@ private void sendFileToRemoteDirectory(InputStream inputStream, String temporary
563571

564572
private void doSend(Session<F> session, FileExistsMode mode, String remoteFilePath, String tempFilePath,
565573
InputStream stream) throws IOException {
574+
566575
boolean rename = this.useTemporaryFileName;
567576
if (FileExistsMode.REPLACE.equals(mode)) {
568577
session.write(stream, tempFilePath);

spring-integration-file/src/test/java/org/springframework/integration/file/remote/RemoteFileTemplateTests.java

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2019 the original author or authors.
2+
* Copyright 2015-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,14 +32,16 @@
3232
import java.io.File;
3333
import java.io.IOException;
3434
import java.io.InputStream;
35+
import java.nio.file.Files;
36+
import java.nio.file.Path;
3537
import java.util.UUID;
3638

37-
import org.junit.Before;
38-
import org.junit.Rule;
39-
import org.junit.Test;
40-
import org.junit.rules.TemporaryFolder;
39+
import org.junit.jupiter.api.BeforeEach;
40+
import org.junit.jupiter.api.Test;
41+
import org.junit.jupiter.api.io.TempDir;
4142

4243
import org.springframework.beans.factory.BeanFactory;
44+
import org.springframework.core.io.ByteArrayResource;
4345
import org.springframework.expression.common.LiteralExpression;
4446
import org.springframework.integration.file.remote.session.Session;
4547
import org.springframework.integration.file.remote.session.SessionFactory;
@@ -61,13 +63,13 @@ public class RemoteFileTemplateTests {
6163

6264
private Session<Object> session;
6365

64-
@Rule
65-
public TemporaryFolder folder = new TemporaryFolder();
66+
@TempDir
67+
Path folder;
6668

6769
private File file;
6870

6971
@SuppressWarnings("unchecked")
70-
@Before
72+
@BeforeEach
7173
public void setUp() throws Exception {
7274
SessionFactory<Object> sessionFactory = mock(SessionFactory.class);
7375
this.template = new RemoteFileTemplate<>(sessionFactory);
@@ -76,7 +78,7 @@ public void setUp() throws Exception {
7678
this.template.afterPropertiesSet();
7779
this.session = mock(Session.class);
7880
when(sessionFactory.getSession()).thenReturn(this.session);
79-
this.file = this.folder.newFile();
81+
this.file = Files.createTempFile(this.folder, null, null).toFile();
8082
}
8183

8284
@Test
@@ -146,6 +148,12 @@ public void testBytes() throws IOException {
146148
verify(this.session).write(any(InputStream.class), any());
147149
}
148150

151+
@Test
152+
public void testResource() throws IOException {
153+
this.template.send(new GenericMessage<>(new ByteArrayResource("foo".getBytes())), FileExistsMode.IGNORE);
154+
verify(this.session).write(any(InputStream.class), any());
155+
}
156+
149157
@Test
150158
public void testMissingFile() {
151159
this.template.send(new GenericMessage<>(new File(UUID.randomUUID().toString())), FileExistsMode.IGNORE);

spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,12 +17,13 @@
1717
package org.springframework.integration.ftp.config;
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2021

22+
import java.nio.charset.StandardCharsets;
2123
import java.util.Iterator;
2224
import java.util.Set;
2325

24-
import org.junit.Test;
25-
import org.junit.runner.RunWith;
26+
import org.junit.jupiter.api.Test;
2627

2728
import org.springframework.beans.factory.BeanCreationException;
2829
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,18 +46,17 @@
4546
import org.springframework.messaging.MessageHandler;
4647
import org.springframework.messaging.support.GenericMessage;
4748
import org.springframework.test.annotation.DirtiesContext;
48-
import org.springframework.test.context.ContextConfiguration;
49-
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
49+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
5050

5151
/**
5252
* @author Oleg Zhurakousky
5353
* @author Gary Russell
5454
* @author Gunnar Hillert
5555
* @author Artem Bilan
56+
*
5657
* @since 2.0
5758
*/
58-
@ContextConfiguration
59-
@RunWith(SpringJUnit4ClassRunner.class)
59+
@SpringJUnitConfig
6060
@DirtiesContext
6161
public class FtpOutboundChannelAdapterParserTests {
6262

@@ -87,7 +87,7 @@ public class FtpOutboundChannelAdapterParserTests {
8787
private FileNameGenerator fileNameGenerator;
8888

8989
@Test
90-
public void testFtpOutboundChannelAdapterComplete() throws Exception {
90+
public void testFtpOutboundChannelAdapterComplete() {
9191
assertThat(TestUtils.getPropertyValue(ftpOutbound, "inputChannel")).isEqualTo(ftpChannel);
9292
assertThat(ftpOutbound.getComponentName()).isEqualTo("ftpOutbound");
9393
FileTransferringMessageHandler<?> handler =
@@ -100,7 +100,7 @@ public void testFtpOutboundChannelAdapterComplete() throws Exception {
100100
assertThat(remoteFileSeparator).isEqualTo("");
101101
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator"))
102102
.isEqualTo(this.fileNameGenerator);
103-
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo("UTF-8");
103+
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset")).isEqualTo(StandardCharsets.UTF_8);
104104
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.directoryExpressionProcessor")).isNotNull();
105105
assertThat(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryDirectoryExpressionProcessor"))
106106
.isNotNull();
@@ -122,9 +122,12 @@ public void testFtpOutboundChannelAdapterComplete() throws Exception {
122122
assertThat(TestUtils.getPropertyValue(ftpOutbound, "handler.mode")).isEqualTo(FileExistsMode.APPEND);
123123
}
124124

125-
@Test(expected = BeanCreationException.class)
126-
public void testFailWithEmptyRfsAndAcdTrue() throws Exception {
127-
new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-fail.xml", this.getClass()).close();
125+
@Test
126+
public void testFailWithEmptyRfsAndAcdTrue() {
127+
assertThatExceptionOfType(BeanCreationException.class)
128+
.isThrownBy(() ->
129+
new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-fail.xml",
130+
getClass()));
128131
}
129132

130133
@Test
@@ -139,7 +142,7 @@ public void cachingByDefault() {
139142
@Test
140143
public void adviceChain() {
141144
MessageHandler handler = TestUtils.getPropertyValue(advisedAdapter, "handler", MessageHandler.class);
142-
handler.handleMessage(new GenericMessage<String>("foo"));
145+
handler.handleMessage(new GenericMessage<>("foo"));
143146
assertThat(adviceCalled).isEqualTo(1);
144147
}
145148

@@ -152,7 +155,7 @@ public void testTemporaryFileSuffix() {
152155
}
153156

154157
@Test
155-
public void testBeanExpressions() throws Exception {
158+
public void testBeanExpressions() {
156159
FileTransferringMessageHandler<?> handler =
157160
TestUtils.getPropertyValue(withBeanExpressions, "handler", FileTransferringMessageHandler.class);
158161
ExpressionEvaluatingMessageProcessor<?> dirExpProc = TestUtils.getPropertyValue(handler,

0 commit comments

Comments
 (0)