001package dk.netarkivet.common.distribute; 002 003import java.io.IOException; 004import java.io.Serializable; 005import java.util.Calendar; 006 007import org.apache.commons.net.ftp.FTPClient; 008import org.apache.commons.net.io.CopyStreamException; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012import dk.netarkivet.common.exceptions.IOFailure; 013import dk.netarkivet.common.utils.SystemUtils; 014import dk.netarkivet.common.utils.TimeUtils; 015 016public class FTPConnectionManager implements Serializable { 017 018 019 /** A named logger for this class. */ 020 private static final transient Logger log = LoggerFactory.getLogger(FTPConnectionManager.class); 021 022 /** The FTP client object for the current connection. */ 023 private transient FTPClient currentFTPClient; 024 025 /** 026 * Ftp-connection information. 027 */ 028 private String ftpServerName; 029 /** The ftp-server port. */ 030 private final int ftpServerPort; 031 /** The username used to connect to the ftp-server. */ 032 private final String ftpUserName; 033 /** The password used to connect to the ftp-server. */ 034 private final String ftpUserPassword; 035 036 private int ftpRetries; 037 038 private int ftpDataTimeout; 039 040 public FTPConnectionManager(String ftpUserName, String ftpUserPassword, 041 String ftpServerName, int ftpServerPort, int ftpRetries, int ftpDataTimeout) { 042 043 this.ftpUserName = ftpUserName; 044 this.ftpUserPassword = ftpUserPassword; 045 this.ftpServerName = ftpServerName; 046 if (ftpServerName.equalsIgnoreCase("localhost")) { 047 this.ftpServerName = SystemUtils.getLocalHostName(); 048 log.debug("ftpServerName set to localhost on machine: {}, resetting to {}", 049 SystemUtils.getLocalHostName(), ftpServerName); 050 } 051 052 this.ftpServerPort = ftpServerPort; 053 this.ftpRetries = ftpRetries; 054 this.ftpDataTimeout = ftpDataTimeout; 055 } 056 057 /** 058 * Create FTPClient and log on to ftp-server, if not already connected to ftp-server. Attempts to set binary mode 059 * and passive mode. Will try to login up to FTP_RETRIES times, if login fails. 060 */ 061 void logOn() { 062 if (currentFTPClient != null && currentFTPClient.isConnected()) { 063 return; 064 } else { // create new FTPClient object and connect to ftp-server 065 currentFTPClient = new FTPClient(); 066 } 067 068 if (log.isDebugEnabled()) { 069 log.trace("Try to logon to ftp://{}:{}@{}:{}", ftpUserName, ftpUserPassword.replaceAll(".", "*"), 070 ftpServerName, ftpServerPort); 071 } 072 073 int tries = 0; 074 boolean logOnSuccessful = false; 075 while (!logOnSuccessful && tries < ftpRetries) { 076 tries++; 077 try { 078 currentFTPClient.connect(ftpServerName, ftpServerPort); 079 currentFTPClient.setDataTimeout(ftpDataTimeout); 080 if (!currentFTPClient.login(ftpUserName, ftpUserPassword)) { 081 final String message = "Could not log in [from host: " + SystemUtils.getLocalHostName() + "] to '" 082 + ftpServerName + "' on port " + ftpServerPort + " with user '" + ftpUserName 083 + "' password '" + ftpUserPassword.replaceAll(".", "*") + "': " + getFtpErrorMessage(); 084 log.warn(message); 085 throw new IOFailure(message); 086 } 087 088 if (!currentFTPClient.setFileType(FTPClient.BINARY_FILE_TYPE)) { 089 final String message = "Could not set binary on '" + ftpServerName 090 + "', losing high bits. Error: " + getFtpErrorMessage(); 091 log.warn(message); 092 throw new IOFailure(message); 093 } 094 095 // This only means that PASV is sent before every transfer 096 // command. 097 currentFTPClient.enterLocalPassiveMode(); 098 099 log.debug("w/ DataTimeout (ms): {}", currentFTPClient.getDefaultTimeout()); 100 logOnSuccessful = true; 101 } catch (IOException e) { 102 final String msg = "Connect to " + ftpServerName + " from host: " + SystemUtils.getLocalHostName() 103 + " failed"; 104 if (tries < ftpRetries) { 105 log.debug( 106 "{}. Attempt #{} of max {}. Will sleep a while before trying to connect again. Exception: ", 107 msg, tries, ftpRetries); 108 TimeUtils.exponentialBackoffSleep(tries, Calendar.MINUTE); 109 } else { 110 log.warn("{}. This was the last (#{}) connection attempt", msg, tries); 111 throw new IOFailure(msg, e); 112 } 113 } 114 } 115 116 if (log.isDebugEnabled()) { 117 log.debug("Logged onto ftp://{}:{}@{}:{}", ftpUserName, ftpUserPassword.replaceAll(".", "*"), 118 ftpServerName, ftpServerPort); 119 } 120 } 121 122 /** 123 * Get the reply code and string from the ftp client. 124 * 125 * @return A string with the FTP servers last reply code and message. 126 */ 127 public String getFtpErrorMessage() { 128 return ("Error " + currentFTPClient.getReplyCode() + ": '" + currentFTPClient.getReplyString() + "'"); 129 } 130 131 /** 132 * Log out from the FTP server. 133 */ 134 void logOut() { 135 log.debug("Trying to log out."); 136 try { 137 if (currentFTPClient != null) { 138 currentFTPClient.disconnect(); 139 } 140 } catch (IOException e) { 141 String msg = "Disconnect from '" + ftpServerName + "' failed "; 142 if (e instanceof CopyStreamException) { 143 CopyStreamException realException = (CopyStreamException) e; 144 msg += "(real cause = " + realException.getIOException() + ")"; 145 } 146 log.warn(msg, e); 147 } 148 } 149 150 public FTPClient getFTPClient() { 151 return currentFTPClient; 152 } 153 154 public String getFtpServer() { 155 return ftpServerName; 156 } 157 158}