001package org.hl7.fhir.utilities;
002
003import lombok.Getter;
004import org.apache.commons.net.ftp.FTP;
005import org.apache.commons.net.ftp.FTPReply;
006import org.hl7.fhir.exceptions.FHIRException;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import java.io.FileInputStream;
011import java.io.IOException;
012
013public class FTPClient {
014
015  private static final Logger logger = LoggerFactory.getLogger(FTPClient.class);
016
017  private final org.apache.commons.net.ftp.FTPClient clientImpl;
018
019  @Getter
020  private final String server;
021
022  @Getter
023  private final String path;
024
025  private String resolvedPath = null;
026
027  @Getter
028  private final String user;
029
030  @Getter
031  private final String password;
032
033  @Getter
034  private final int port;
035
036  private final String remoteSeparator;
037
038  /**
039   * Connect to an FTP server
040   * @param server - the server to connect to (usually just an IP address). It's up to the system to figure out access (VPN etc)
041   * @param path - the path on the FTP server to treat all the operations as relative to 
042   * @param user - username for the FTP server
043   * @param password - password for the FTP server
044   */
045  public FTPClient(String server, String path, String user, String password) {
046    this (server, -1, path, user, password);
047  }
048
049  protected FTPClient(String server, int port, String path, String user, String password) {
050    this.server = server;
051    this.port = port;
052    this.remoteSeparator = "/";
053    this.path = buildPath(path);
054
055    this.user = user;
056    this.password = password;
057
058    clientImpl = new org.apache.commons.net.ftp.FTPClient();
059  }
060
061  private String buildPath(String path) {
062    if (path.length() == 0) {
063      return "";
064    }
065    if (path.endsWith(remoteSeparator))
066    {
067      return path;
068    }
069    return path + remoteSeparator;
070  }
071
072  /**
073   * Connect to the server, throw an exception if it fails
074   */
075  public void connect() throws IOException {
076    if (port != -1) {
077      clientImpl.connect(server, port);
078    }
079    else {
080      clientImpl.connect(server);
081    }
082
083    clientImpl.login(user, password);
084
085    checkForPositiveCompletionAndLogErrors("FTP server could not connect.", true);
086
087    logger.debug("Initial Working directory: " + clientImpl.printWorkingDirectory());
088
089    clientImpl.changeWorkingDirectory(path);
090
091    checkForPositiveCompletionAndLogErrors("FTP server could not establish default working directory", true);
092
093    resolvedPath = clientImpl.printWorkingDirectory();
094
095    logger.debug("Resolved working directory: " + resolvedPath);
096  }
097
098  /**
099   * Delete a file on the FTP server
100   * 
101   * @param path - relative to the path provided in the constructor 
102   */
103  public void delete(String path) throws IOException {
104    String resolvedPath = resolveRemotePath(path);
105    logger.debug("Deleting remote file: " + resolvedPath);
106    clientImpl.deleteFile(resolvedPath);
107    checkForPositiveCompletionAndLogErrors("Error deleting file.", false);
108    logger.debug("Remote file deleted: " + resolvedPath);
109  }
110
111  /**
112   * Takes a file path and creates all intermediate directories if they do not yet exist.
113   * @param filePath relative to the path provided in the constructor and including the file name
114   * @throws IOException
115   */
116  protected void createRemotePathIfNotExists(String filePath) throws IOException {
117    String[] subPath = filePath.split(remoteSeparator);
118    try {
119    for (int i = 0 ; i < subPath.length - 1; i++){
120      if (subPath[i].isEmpty() ) {
121        continue;
122      }
123      boolean exists = clientImpl.changeWorkingDirectory(subPath[i]);
124      if (!exists) {
125        logger.debug("Remote directory does not exist: " + clientImpl.printWorkingDirectory() + remoteSeparator + subPath[i]);
126        clientImpl.makeDirectory(subPath[i]);
127        clientImpl.changeWorkingDirectory(subPath[i]);
128        logger.debug("Made remote directory: " + clientImpl.printWorkingDirectory());
129      }
130    }} catch (IOException e) {
131      throw new IOException("Error creating remote path: " + filePath, e);
132    } finally {
133      clientImpl.changeWorkingDirectory(this.resolvedPath);
134    }
135  }
136
137  protected boolean remotePathExists(String path) throws IOException {
138    boolean output;
139    try {
140      output = clientImpl.changeWorkingDirectory(path);
141    } finally {
142      clientImpl.changeWorkingDirectory(this.resolvedPath);
143    }
144    return output;
145  }
146
147  private String resolveRemotePath(String path) {
148    if (path.startsWith(remoteSeparator)) {
149      throw new IllegalArgumentException("Absolute remote path is not permitted. Path: " + path);
150    }
151    return String.join(remoteSeparator, path);
152  }
153
154  /**
155   * Upload a file from the local system to the FTP Server
156   * @param source - absolute path on local system
157   * @param path - relative to the path provided in the constructor
158   */
159  public void upload(String source, String path) throws IOException {
160    String resolvedPath = resolveRemotePath(path);
161    logger.debug("Uploading file to remote path: " + resolvedPath);
162    createRemotePathIfNotExists(path);
163
164    FileInputStream localStream = new FileInputStream(source);
165    clientImpl.setFileType(FTP.BINARY_FILE_TYPE);
166    clientImpl.enterLocalPassiveMode();
167    clientImpl.storeFile( resolvedPath, localStream);
168    localStream.close();
169
170    checkForPositiveCompletionAndLogErrors("Error uploading file.", false);
171    logger.debug("Remote file uploaded: " + resolvedPath);
172  }
173
174  private void checkForPositiveCompletionAndLogErrors(String localErrorMessage, boolean disconnectOnError) throws IOException {
175    int reply = clientImpl.getReplyCode();
176
177    if (FTPReply.isPositiveCompletion(reply)) {
178      return;
179    }
180
181    String remoteErrorMessage = clientImpl.getReplyString();
182    if (disconnectOnError) {
183      clientImpl.disconnect();
184    }
185    throw new IOException(localErrorMessage + " Reply code: " + reply + " Message: " + remoteErrorMessage);
186
187
188  }
189
190  public void disconnect() throws IOException {
191    clientImpl.disconnect();
192  }
193
194
195  public static void main(String[] args) throws IOException, FHIRException {
196    FTPClient ftp = new FTPClient(getNamedParam(args, "-upload-server"), getNamedParam(args, "-upload-path"), getNamedParam(args, "-upload-user"), getNamedParam(args, "-upload-password"));
197    ftp.connect();
198    ftp.upload("/Users/grahamegrieve/temp/test.xml", "testing/test.xml");
199    ftp.delete("testing/test.xml");
200    ftp.disconnect();
201  }
202
203  private static String getNamedParam(String[] args, String param) {
204    boolean found = false;
205    for (String a : args) {
206      if (found)
207        return a;
208      if (a.equals(param)) {
209        found = true;
210      }
211    }
212    return null;
213  }
214
215  
216}