Skip to content

Commit

Permalink
FTPClient: upload file path creation, logging, and improved default p…
Browse files Browse the repository at this point in the history
…ath (#1078)

* More logging of errors.

* WIP create path for file to upload.

* WIP Tests

* More tests

* Improved logging and path fixes

* Clean up imports

* Rename test

* add testing code

Co-authored-by: dotasek <[email protected]>
Co-authored-by: Grahame Grieve <[email protected]>
  • Loading branch information
3 people authored Jan 18, 2023
1 parent d707131 commit 885e553
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
package org.hl7.fhir.utilities;

import lombok.Getter;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPReply;
import org.hl7.fhir.exceptions.FHIRException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class FTPClient {

private static final Logger logger = LoggerFactory.getLogger(FTPClient.class);

private final org.apache.commons.net.ftp.FTPClient clientImpl;

final String server;
@Getter
private final String server;

@Getter
private final String path;

final String path;
private String resolvedPath = null;

final String user;
@Getter
private final String user;

final String password;
@Getter
private final String password;

final int port;
@Getter
private final int port;

private final String remoteSeparator;

/**
* Connect to an FTP server
Expand All @@ -36,17 +49,26 @@ public FTPClient(String server, String path, String user, String password) {
protected FTPClient(String server, int port, String path, String user, String password) {
this.server = server;
this.port = port;
if (path.endsWith("/")) {
this.path = path;
} else {
this.path = path + "/";
}
this.remoteSeparator = "/";
this.path = buildPath(path);

this.user = user;
this.password = password;

clientImpl = new org.apache.commons.net.ftp.FTPClient();
}

private String buildPath(String path) {
if (path.length() == 0) {
return "";
}
if (path.endsWith(remoteSeparator))
{
return path;
}
return path + remoteSeparator;
}

/**
* Connect to the server, throw an exception if it fails
*/
Expand All @@ -60,13 +82,17 @@ public void connect() throws IOException {

clientImpl.login(user, password);

checkForPositiveCompletionAndLogErrors("FTP server could not connect.", true);

int reply = clientImpl.getReplyCode();
logger.debug("Initial Working directory: " + clientImpl.printWorkingDirectory());

if(!FTPReply.isPositiveCompletion(reply)) {
clientImpl.disconnect();
throw new IOException("FTP server refused connection. Reply code: " + reply);
}
clientImpl.changeWorkingDirectory(path);

checkForPositiveCompletionAndLogErrors("FTP server could not establish default working directory", true);

resolvedPath = clientImpl.printWorkingDirectory();

logger.debug("Resolved working directory: " + resolvedPath);
}

/**
Expand All @@ -76,11 +102,53 @@ public void connect() throws IOException {
*/
public void delete(String path) throws IOException {
String resolvedPath = resolveRemotePath(path);
logger.debug("Deleting remote file: " + resolvedPath);
clientImpl.deleteFile(resolvedPath);
checkForPositiveCompletionAndLogErrors("Error deleting file.", false);
logger.debug("Remote file deleted: " + resolvedPath);
}

/**
* Takes a file path and creates all intermediate directories if they do not yet exist.
* @param filePath relative to the path provided in the constructor and including the file name
* @throws IOException
*/
protected void createRemotePathIfNotExists(String filePath) throws IOException {
String[] subPath = filePath.split(remoteSeparator);
try {
for (int i = 0 ; i < subPath.length - 1; i++){
if (subPath[i].isEmpty() ) {
continue;
}
boolean exists = clientImpl.changeWorkingDirectory(subPath[i]);
if (!exists) {
logger.debug("Remote directory does not exist: " + clientImpl.printWorkingDirectory() + remoteSeparator + subPath[i]);
clientImpl.makeDirectory(subPath[i]);
clientImpl.changeWorkingDirectory(subPath[i]);
logger.debug("Made remote directory: " + clientImpl.printWorkingDirectory());
}
}} catch (IOException e) {
throw new IOException("Error creating remote path: " + filePath, e);
} finally {
clientImpl.changeWorkingDirectory(this.resolvedPath);
}
}

protected boolean remotePathExists(String path) throws IOException {
boolean output;
try {
output = clientImpl.changeWorkingDirectory(path);
} finally {
clientImpl.changeWorkingDirectory(this.resolvedPath);
}
return output;
}

private String resolveRemotePath(String path) {
return String.join("", this.path, path);
if (path.startsWith(remoteSeparator)) {
throw new IllegalArgumentException("Absolute remote path is not permitted. Path: " + path);
}
return String.join(remoteSeparator, path);
}

/**
Expand All @@ -90,21 +158,59 @@ private String resolveRemotePath(String path) {
*/
public void upload(String source, String path) throws IOException {
String resolvedPath = resolveRemotePath(path);
logger.debug("Uploading file to remote path: " + resolvedPath);
createRemotePathIfNotExists(path);

FileInputStream localStream = new FileInputStream(source);
clientImpl.setFileType(FTP.BINARY_FILE_TYPE);
clientImpl.enterLocalPassiveMode();
clientImpl.storeFile( resolvedPath, localStream);
localStream.close();

checkForPositiveCompletionAndLogErrors("Error uploading file.", false);
logger.debug("Remote file uploaded: " + resolvedPath);
}

private void checkForPositiveCompletionAndLogErrors(String localErrorMessage, boolean disconnectOnError) throws IOException {
int reply = clientImpl.getReplyCode();

if(!FTPReply.isPositiveCompletion(reply)) {
throw new IOException("Error uploading file. Reply code: " + reply);
if (FTPReply.isPositiveCompletion(reply)) {
return;
}

String remoteErrorMessage = clientImpl.getReplyString();
if (disconnectOnError) {
clientImpl.disconnect();
}
throw new IOException(localErrorMessage + " Reply code: " + reply + " Message: " + remoteErrorMessage);


}

public void disconnect() throws IOException {
clientImpl.disconnect();
}


public static void main(String[] args) throws IOException, FHIRException {
FTPClient ftp = new FTPClient(getNamedParam(args, "-upload-server"), getNamedParam(args, "-upload-path"), getNamedParam(args, "-upload-user"), getNamedParam(args, "-upload-password"));
ftp.connect();
ftp.upload("/Users/grahamegrieve/temp/test.xml", "testing/test.xml");
ftp.delete("testing/test.xml");
ftp.disconnect();
}

private static String getNamedParam(String[] args, String param) {
boolean found = false;
for (String a : args) {
if (found)
return a;
if (a.equals(param)) {
found = true;
}
}
return null;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

import org.apache.commons.io.IOUtils;
import org.hl7.fhir.utilities.tests.ResourceLoaderTests;
import org.jetbrains.annotations.NotNull;

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockftpserver.fake.FakeFtpServer;
import org.mockftpserver.fake.UserAccount;
import org.mockftpserver.fake.filesystem.*;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
Expand All @@ -31,6 +32,7 @@ public class FTPClientTest implements ResourceLoaderTests {
public static final String DUMMY_FILE_TO_UPLOAD = "dummyFileToUpload";
public static final int FAKE_FTP_PORT = 8021;
public static final String DUMMY_FILE_CONTENT = "Dummy file content\nMore content\n";
public static final String LOCALHOST = "localhost";


FakeFtpServer fakeFtpServer;
Expand Down Expand Up @@ -69,8 +71,6 @@ private String createDummyFileContent() {
}

public void setupFakeFtpServer() throws IOException {


fakeFtpServer = new FakeFtpServer();
fakeFtpServer.setServerControlPort(FAKE_FTP_PORT);
fakeFtpServer.addUserAccount(new UserAccount(DUMMY_USER, DUMMY_PASSWORD, fakeFtpDirectory.toFile().getAbsolutePath()));
Expand Down Expand Up @@ -108,6 +108,21 @@ public void tearDownFakeFtpServer() {
fakeFtpServer.stop();
}

@ParameterizedTest
@CsvSource({"/", "/test", "/test", "/test/", "/test1/test2", "/test1/test2", "test", "test/", "test1/test2"})
public void testValidRelativePaths(String path) {
FTPClient client = new FTPClient("localhost", path, DUMMY_USER, DUMMY_PASSWORD);
assertTrue(path.length() == client.getPath().length() || path.length() + 1 == client.getPath().length());
assertTrue(client.getPath().startsWith(path));
assertTrue(client.getPath().endsWith("/"));
}

@Test
public void testEmptyRelativePath() {
FTPClient client = new FTPClient("localhost", "", DUMMY_USER, DUMMY_PASSWORD);
assertEquals("", client.getPath());
}

@Test
public void testDelete() throws IOException {

Expand All @@ -120,17 +135,16 @@ public void testDelete() throws IOException {
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);
FTPClient client = new FTPClient(LOCALHOST, FAKE_FTP_PORT, RELATIVE_PATH_1, DUMMY_USER, DUMMY_PASSWORD);
client.connect();
return client;
}

@Test
public void testDelete2() throws IOException {

FTPClient client = connectToFTPClient2();
FTPClient client = connectToFTPClient();

String deleteFilePath = dummyFileToDeletePath.toFile().getAbsolutePath();
assertTrue(fakeFtpServer.getFileSystem().exists(deleteFilePath));
Expand All @@ -140,30 +154,65 @@ public void testDelete2() throws IOException {
client.disconnect();
}

@NotNull
private static FTPClient connectToFTPClient2() 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));
assertUploadedFileCorrect(uploadFilePath);
}

FileEntry fileEntry= (FileEntry)fakeFtpServer.getFileSystem().getEntry(uploadFilePath);
private void assertUploadedFileCorrect(String uploadedFilePath) throws IOException {
assertTrue(fakeFtpServer.getFileSystem().exists(uploadedFilePath));
FileEntry fileEntry = (FileEntry)fakeFtpServer.getFileSystem().getEntry(uploadedFilePath);
assertNotNull(fileEntry);
InputStream inputStream = fileEntry.createInputStream();
byte[] bytes = IOUtils.toByteArray(inputStream);
String actualContent = new String(bytes, StandardCharsets.UTF_8);
assertEquals(DUMMY_FILE_CONTENT,actualContent);
}

@Test
public void testCreateRemotePathDoesntExist() throws IOException {
FTPClient client = connectToFTPClient();

Path newPath1 = relativePath2.resolve("newPath1");
Path newPath2 = newPath1.resolve("newPath2");

assertFalse(fakeFtpServer.getFileSystem().exists(newPath1.toFile().getAbsolutePath()));
assertFalse(fakeFtpServer.getFileSystem().exists(newPath2.toFile().getAbsolutePath()));

client.createRemotePathIfNotExists(RELATIVE_PATH_2 + "/newPath1/newPath2/newFile.txt");

assertTrue(fakeFtpServer.getFileSystem().exists(newPath1.toFile().getAbsolutePath()));
assertTrue(fakeFtpServer.getFileSystem().exists(newPath2.toFile().getAbsolutePath()));

}

@Test
public void testUploadWherePathDoesntExist() throws IOException {
Path newPath1 = relativePath2.resolve("newPath1");
Path newPath2 = newPath1.resolve("newPath2");

FTPClient client = connectToFTPClient();

Path uploadFilePath = newPath2.resolve(DUMMY_FILE_TO_UPLOAD);
assertFalse(fakeFtpServer.getFileSystem().exists(uploadFilePath.toFile().getAbsolutePath()));

assertFalse(fakeFtpServer.getFileSystem().exists(newPath1.toFile().getAbsolutePath()));
assertFalse(fakeFtpServer.getFileSystem().exists(newPath2.toFile().getAbsolutePath()));

client.upload(dummyFileToUploadPath.toFile().getAbsolutePath(),RELATIVE_PATH_2 + "/newPath1/newPath2/" + DUMMY_FILE_TO_UPLOAD);

assertTrue(fakeFtpServer.getFileSystem().exists(newPath1.toFile().getAbsolutePath()));
assertTrue(fakeFtpServer.getFileSystem().exists(newPath2.toFile().getAbsolutePath()));

assertUploadedFileCorrect(uploadFilePath.toFile().getAbsolutePath());
}
}

0 comments on commit 885e553

Please sign in to comment.