Implement FTP Client (#1064)
* Start with tests against fake FTP * Dummy test file * Switch to our FTPClient, make tests break * Implement FTPClient * Rely on end caller to set path delimiters * User port 8021 for tests (not protected in azure pipelines) * Let mocks use Windows filesystem * Gentle refactor Co-authored-by: dotasek <david.otasek@smilecdr.com>
This commit is contained in:
parent
02cdad6f68
commit
edf6d75551
|
@ -86,6 +86,12 @@
|
||||||
<version>72.1</version>
|
<version>72.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-net</groupId>
|
||||||
|
<artifactId>commons-net</artifactId>
|
||||||
|
<version>3.6</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- JUnit Jupiter -->
|
<!-- JUnit Jupiter -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
@ -142,7 +148,12 @@
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockftpserver</groupId>
|
||||||
|
<artifactId>MockFtpServer</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -1,21 +1,67 @@
|
||||||
package org.hl7.fhir.utilities;
|
package org.hl7.fhir.utilities;
|
||||||
|
|
||||||
|
import org.apache.commons.net.ftp.FTPReply;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public class FTPClient {
|
public class FTPClient {
|
||||||
|
|
||||||
|
private final org.apache.commons.net.ftp.FTPClient clientImpl;
|
||||||
|
|
||||||
|
final String server;
|
||||||
|
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
final String user;
|
||||||
|
|
||||||
|
final String password;
|
||||||
|
|
||||||
|
final int port;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to an FTP server
|
* Connect to an FTP server
|
||||||
* @param server - the server to connect to (uusally just an IP address). It's up to the system to figure out access (VPN etc)
|
* @param server - the server to connect to (usually just an IP address). It's up to the system to figure out access (VPN etc)
|
||||||
* @param path - the path on the FTP server to treat all the operations as relative to
|
* @param path - the path on the FTP server to treat all the operations as relative to
|
||||||
* @param user - username for the FTP server
|
* @param user - username for the FTP server
|
||||||
* @param password - password for the FTP server
|
* @param password - password for the FTP server
|
||||||
*/
|
*/
|
||||||
public FTPClient(String server, String path, String user, String password) {
|
public FTPClient(String server, String path, String user, String password) {
|
||||||
|
this (server, -1, path, user, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected FTPClient(String server, int port, String path, String user, String password) {
|
||||||
|
this.server = server;
|
||||||
|
this.port = port;
|
||||||
|
this.path = path;
|
||||||
|
this.user = user;
|
||||||
|
this.password = password;
|
||||||
|
|
||||||
|
clientImpl = new org.apache.commons.net.ftp.FTPClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to the server, throw an exception if it fails
|
* Connect to the server, throw an exception if it fails
|
||||||
*/
|
*/
|
||||||
public void connect() {
|
public void connect() throws IOException {
|
||||||
|
if (port != -1) {
|
||||||
|
clientImpl.connect(server, port);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clientImpl.connect(server);
|
||||||
|
}
|
||||||
|
clientImpl.login(user, password);
|
||||||
|
|
||||||
|
clientImpl.getSystemType();
|
||||||
|
|
||||||
|
int reply = clientImpl.getReplyCode();
|
||||||
|
|
||||||
|
if(!FTPReply.isPositiveCompletion(reply)) {
|
||||||
|
clientImpl.disconnect();
|
||||||
|
throw new IOException("FTP server refused connection.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +69,13 @@ public class FTPClient {
|
||||||
*
|
*
|
||||||
* @param path - relative to the path provided in the constructor
|
* @param path - relative to the path provided in the constructor
|
||||||
*/
|
*/
|
||||||
public void delete(String path) {
|
public void delete(String path) throws IOException {
|
||||||
|
String resolvedPath = resolveRemotePath(path);
|
||||||
|
clientImpl.deleteFile(resolvedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveRemotePath(String path) {
|
||||||
|
return String.join("", this.path, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,9 +83,10 @@ public class FTPClient {
|
||||||
* @param source - absolute path on local system
|
* @param source - absolute path on local system
|
||||||
* @param path - relative to the path provided in the constructor
|
* @param path - relative to the path provided in the constructor
|
||||||
*/
|
*/
|
||||||
public void upload(String source, String path) {
|
public void upload(String source, String path) throws IOException {
|
||||||
|
String resolvedPath = resolveRemotePath(path);
|
||||||
|
FileInputStream localStream = new FileInputStream(new File(source));
|
||||||
|
clientImpl.storeFile( resolvedPath, localStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
package org.hl7.fhir.utilities;
|
||||||
|
|
||||||
|
|
||||||
|
import org.hl7.fhir.utilities.tests.ResourceLoaderTests;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestInstance;
|
||||||
|
import org.mockftpserver.fake.FakeFtpServer;
|
||||||
|
import org.mockftpserver.fake.UserAccount;
|
||||||
|
import org.mockftpserver.fake.filesystem.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
public class FTPClientTest implements ResourceLoaderTests {
|
||||||
|
|
||||||
|
public static final String DUMMY_PASSWORD = "dummyPassword123";
|
||||||
|
public static final String DUMMY_USER = "dummyUser";
|
||||||
|
public static final String RELATIVE_PATH_1 = "relativePath1";
|
||||||
|
|
||||||
|
public static final String RELATIVE_PATH_2 = "relativePath2";
|
||||||
|
public static final String DUMMY_FILE_TO_DELETE = "dummyFileToDelete";
|
||||||
|
|
||||||
|
public static final String DUMMY_FILE_TO_UPLOAD = "dummyFileToUpload";
|
||||||
|
public static final int FAKE_FTP_PORT = 8021;
|
||||||
|
|
||||||
|
|
||||||
|
FakeFtpServer fakeFtpServer;
|
||||||
|
|
||||||
|
Path fakeFtpDirectory;
|
||||||
|
|
||||||
|
Path relativePath1;
|
||||||
|
|
||||||
|
Path relativePath2;
|
||||||
|
|
||||||
|
|
||||||
|
Path dummyFileToDeletePath;
|
||||||
|
|
||||||
|
|
||||||
|
Path dummyFileToUploadPath;
|
||||||
|
Path dummyUploadedFilePath;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public void setup() throws IOException {
|
||||||
|
setupDummyFileToUpload();
|
||||||
|
setupFakeFtpDirectory();
|
||||||
|
setupFakeFtpServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupDummyFileToUpload() throws IOException {
|
||||||
|
dummyFileToUploadPath = Files.createTempFile("dummyFtpFileToUpload", "dummy");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupFakeFtpServer() throws IOException {
|
||||||
|
|
||||||
|
|
||||||
|
fakeFtpServer = new FakeFtpServer();
|
||||||
|
fakeFtpServer.setServerControlPort(FAKE_FTP_PORT);
|
||||||
|
fakeFtpServer.addUserAccount(new UserAccount(DUMMY_USER, DUMMY_PASSWORD, fakeFtpDirectory.toFile().getAbsolutePath()));
|
||||||
|
|
||||||
|
FileSystem fileSystem = useWindowsFileSystem()
|
||||||
|
? new WindowsFakeFileSystem()
|
||||||
|
: new UnixFakeFileSystem();
|
||||||
|
fileSystem.add(new DirectoryEntry(fakeFtpDirectory.toFile().getAbsolutePath()));
|
||||||
|
fileSystem.add(new DirectoryEntry(relativePath1.toFile().getAbsolutePath()));
|
||||||
|
fileSystem.add(new DirectoryEntry(relativePath2.toFile().getAbsolutePath()));
|
||||||
|
fileSystem.add(new FileEntry(dummyFileToDeletePath.toFile().getAbsolutePath()));
|
||||||
|
//fileSystem.add(new FileEntry("c:\\data\\run.exe"));
|
||||||
|
fakeFtpServer.setFileSystem(fileSystem);
|
||||||
|
fakeFtpServer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean useWindowsFileSystem() {
|
||||||
|
return System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Windows");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupFakeFtpDirectory() throws IOException {
|
||||||
|
fakeFtpDirectory = Files.createTempDirectory("fakeFtp");
|
||||||
|
relativePath1 = fakeFtpDirectory.resolve(RELATIVE_PATH_1);
|
||||||
|
relativePath2 = relativePath1.resolve(RELATIVE_PATH_2);
|
||||||
|
Files.createDirectory(relativePath1);
|
||||||
|
Files.createDirectory(relativePath2);
|
||||||
|
|
||||||
|
dummyFileToDeletePath = Files.createFile(relativePath2.resolve(DUMMY_FILE_TO_DELETE));
|
||||||
|
dummyUploadedFilePath = relativePath2.resolve(DUMMY_FILE_TO_UPLOAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public void tearDownFakeFtpServer() {
|
||||||
|
fakeFtpServer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDelete() throws IOException {
|
||||||
|
|
||||||
|
FTPClient client = connectToFTPClient();
|
||||||
|
|
||||||
|
|
||||||
|
String deleteFilePath = dummyFileToDeletePath.toFile().getAbsolutePath();
|
||||||
|
|
||||||
|
assertTrue(fakeFtpServer.getFileSystem().exists(deleteFilePath));
|
||||||
|
|
||||||
|
client.delete( RELATIVE_PATH_2 + "/" + DUMMY_FILE_TO_DELETE);
|
||||||
|
|
||||||
|
assertFalse(fakeFtpServer.getFileSystem().exists(deleteFilePath));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static FTPClient connectToFTPClient() throws IOException {
|
||||||
|
FTPClient client = new FTPClient("localhost", FAKE_FTP_PORT, RELATIVE_PATH_1 + "/", DUMMY_USER, DUMMY_PASSWORD);
|
||||||
|
client.connect();
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpload() throws IOException {
|
||||||
|
|
||||||
|
FTPClient client = connectToFTPClient();
|
||||||
|
|
||||||
|
String uploadFilePath = dummyUploadedFilePath.toFile().getAbsolutePath();
|
||||||
|
|
||||||
|
assertFalse(fakeFtpServer.getFileSystem().exists(uploadFilePath));
|
||||||
|
|
||||||
|
client.upload(dummyFileToUploadPath.toFile().getAbsolutePath(), RELATIVE_PATH_2 + "/" + DUMMY_FILE_TO_UPLOAD);
|
||||||
|
|
||||||
|
assertTrue(fakeFtpServer.getFileSystem().exists(uploadFilePath));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue