Java tutorial
/** * Copyright (c) 2014 by the original author or authors. * * This code is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package ch.sdi.core.impl.ftp; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.InetAddress; import java.util.Arrays; import java.util.Map; import org.apache.commons.net.PrintCommandListener; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPConnectionClosedException; import org.apache.commons.net.ftp.FTPHTTPClient; import org.apache.commons.net.ftp.FTPReply; import org.apache.commons.net.ftp.FTPSClient; import org.apache.commons.net.util.TrustManagerUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import ch.sdi.core.exc.SdiException; import ch.sdi.core.intf.SdiMainProperties; /** * The implementation of this class is inspired by the official example * (http://commons.apache.org/proper/commons-net/examples/ftp/FTPClientExample.java). * <p> * The available options of the original were stripped off in order only to use it as file uploader in * binary mode (stripped off options see below). * <p> * Also, the file(s) to upload are not given as arguments but as separate Map<sourceStream,destFileName> * in executeUpload(). So it's possible to transfer more than one file in one session. * <p> * The remaining valid "command line" arguments (given as array of String to the init() method): * <pre> * Usage: [options] <hostname> <username> <password> * </pre> * Options: * <ul> * <li>-a - use local active mode (default is local passive)</li> * <li>-A - anonymous login (omit username and password parameters)</li> * <li>-e - use EPSV with IPv4 (default false)</li> * <li>-k secs - use keep-alive timer (setControlKeepAliveTimeout)</li> * <li>-p true|false|protocol[,true|false] - use FTPSClient with the specified protocol and/or isImplicit setting</li> * <li>-w msec - wait time for keep-alive reply (setControlKeepAliveReplyTimeout)</li> * <li>-T all|valid|none - use one of the built-in TrustManager implementations (none = JVM default)</li> * <li>-PrH server[:port] - HTTP Proxy host and optional port[80] </li> * <li>-PrU user - HTTP Proxy server username</li> * <li>-PrP password - HTTP Proxy server password</li> * </ul> * Removed options (compared to the apache original): * <ul> * <li>-# - add hash display during transfers</li> * <li>-b - use binary transfer mode</li> * <li>-c cmd - issue arbitrary command (remote is used as a parameter if provided)</li> * <li>-d - list directory details using MLSD (remote is used as the pathname if provided)</li> * <li>-f - issue FEAT command (remote and local files are ignored)</li> * <li>-h - list hidden files (applies to -l and -n only)</li> * <li>-l - list files using LIST (remote is used as the pathname if provided)</li> * <li>-L - use lenient future dates (server dates may be up to 1 day into future)</li> * <li>-n - list file names using NLST (remote is used as the pathname if provided)</li> * <li>-s - store file on server (upload)</li> * <li>-t - list file details using MLST (remote is used as the pathname if provided)</li> * </ul> * * @version 1.0 (22.11.2014) * @author Heri */ @Component @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class FtpExecutor { public static final String USAGE = "Usage: [options] <hostname> <username> <password>\n" + "\nDefault behavior is to upload file(s) and use binary transfer mode.\n" + "\t-a - use local active mode (default is local passive)\n" + "\t-A - anonymous login (omit username and password parameters)\n" + "\t-e - use EPSV with IPv4 (default false)\n" + "\t-k secs - use keep-alive timer (setControlKeepAliveTimeout)\n" + "\t-p true|false|protocol[,true|false] - use FTPSClient with the specified protocol and/or isImplicit setting\n" + "\t-w msec - wait time for keep-alive reply (setControlKeepAliveReplyTimeout)\n" + "\t-T all|valid|none - use one of the built-in TrustManager implementations (none = JVM default)\n" + "\t-PrH server[:port] - HTTP Proxy host and optional port[80] \n" + "\t-PrU user - HTTP Proxy server username\n" + "\t-PrP password - HTTP Proxy server password\n"; /** logger for this class */ private Logger myLog = LogManager.getLogger(FtpExecutor.class); @Autowired private Environment myEnv; private boolean myLocalActive = false; private boolean myUseEpsvWithIPv4 = false; private long myKeepAliveTimeout = -1; private int myControlKeepAliveReplyTimeout = -1; private String myProtocol = null; // SSL protocol private String myTrustmgr = null; private String myProxyHost = null; private int myProxyPort = 80; private String myProxyUser = null; private String myProxyPassword = null; private String myUsername = null; private String myPassword = null; private int myPort; private String myServer; private FTPClient myFtp; private PrintCommandToLoggerListener myPrintCommandToLoggerListener; private boolean myInitialized; /** * @param aArgs */ public void init(String[] aArgs) throws SdiException { try { myLog.debug("analyzing given arguments: " + StringUtils.arrayToCommaDelimitedString(aArgs)); int base = 0; int minParams = 3; // listings require 3 params for (base = 0; base < aArgs.length; base++) { if (aArgs[base].equals("-a")) { myLocalActive = true; } else if (aArgs[base].equals("-A")) { myPassword = System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName(); myUsername = "anonymous"; } else if (aArgs[base].equals("-e")) { myUseEpsvWithIPv4 = true; } else if (aArgs[base].equals("-k")) { myKeepAliveTimeout = Long.parseLong(aArgs[++base]); } else if (aArgs[base].equals("-p")) { myProtocol = aArgs[++base]; } else if (aArgs[base].equals("-w")) { myControlKeepAliveReplyTimeout = Integer.parseInt(aArgs[++base]); } else if (aArgs[base].equals("-T")) { myTrustmgr = aArgs[++base]; } else if (aArgs[base].equals("-PrH")) { myProxyHost = aArgs[++base]; String parts[] = myProxyHost.split(":"); if (parts.length == 2) { myProxyHost = parts[0]; myProxyPort = Integer.parseInt(parts[1]); } } else if (aArgs[base].equals("-PrU")) { myProxyUser = aArgs[++base]; } else if (aArgs[base].equals("-PrP")) { myProxyPassword = aArgs[++base]; } else { if (aArgs[base].startsWith("-")) { myLog.warn("unhandled argument: " + aArgs[base]); continue; } // if aArgs[base].startsWith( "-" ) break; } } int remain = aArgs.length - base; if (myUsername != null) { minParams -= 2; } if (remain < minParams) // server, user, pass, remote, local [protocol] { throw createFtpException("Error: Too less params"); } myServer = aArgs[base++]; myPort = 0; String parts[] = myServer.split(":"); if (parts.length == 2) { myServer = parts[0]; myPort = Integer.parseInt(parts[1]); } if (myUsername == null) { myUsername = aArgs[base++]; myPassword = aArgs[base++]; } myInitialized = true; } catch (Throwable t) { myLog.error("Exception caught: ", t); myLog.info(USAGE); if (t instanceof SdiException) { throw (SdiException) t; } // if t instanceof SdiException throw createFtpException("inititalization problems", t); } } /** * @throws SdiException * */ private void initBySpringContext() throws SdiException { String cmdLine = myEnv.getProperty(SdiMainProperties.KEY_FTP_CMD_LINE); myLog.debug("FTP command line read from spring context: " + cmdLine); if (!StringUtils.hasText(cmdLine)) { throw new SdiException("Property " + SdiMainProperties.KEY_FTP_CMD_LINE + " not found", SdiException.EXIT_CODE_CONFIG_ERROR); } // if !StringUtils.hasText( cmdLine ) String[] args = cmdLine.split(" "); init(args); } /** * @throws SdiException * @throws IOException */ public void connectAndLogin() throws SdiException, IOException { if (!myInitialized) { initBySpringContext(); } // if !initialized try { if (myProtocol == null) { if (myProxyHost != null) { myLog.debug("Using HTTP proxy server: " + myProxyHost); myFtp = new FTPHTTPClient(myProxyHost, myProxyPort, myProxyUser, myProxyPassword); } else { myLog.debug("Using simple FTPClient"); myFtp = new FTPClient(); } } else { FTPSClient ftps; if (myProtocol.equals("true")) { myLog.debug("Using FTPSClient with implicite SSL"); ftps = new FTPSClient(true); } else if (myProtocol.equals("false")) { myLog.debug("Using FTPSClient with no implicite SSL"); ftps = new FTPSClient(false); } else { String prot[] = myProtocol.split(","); if (prot.length == 1) { // Just protocol myLog.debug("Using FTPSClient with protocol " + myProtocol); ftps = new FTPSClient(myProtocol); } else { // protocol,true|false myLog.debug("Using FTPSClient with " + prot[0] + " and " + prot[1]); ftps = new FTPSClient(prot[0], Boolean.parseBoolean(prot[1])); } } myFtp = ftps; if ("all".equals(myTrustmgr)) { myLog.debug("Using AcceptAllTrustManager"); ftps.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager()); } else if ("valid".equals(myTrustmgr)) { myLog.debug("Using ValidateServerCertificateTrustManager"); ftps.setTrustManager(TrustManagerUtils.getValidateServerCertificateTrustManager()); } else if ("none".equals(myTrustmgr)) { myLog.debug("Setting TrustManager to null"); ftps.setTrustManager(null); } else { myLog.debug("Using no TrustManager at all"); } } if (myKeepAliveTimeout >= 0) { myLog.debug("Setting KeepAliveTimeout to " + myKeepAliveTimeout); myFtp.setControlKeepAliveTimeout(myKeepAliveTimeout); } if (myControlKeepAliveReplyTimeout >= 0) { myLog.debug("Setting ControlKeepAliveReplyTimeout to " + myControlKeepAliveReplyTimeout); myFtp.setControlKeepAliveReplyTimeout(myControlKeepAliveReplyTimeout); } // intercept commands and write it to our logger myPrintCommandToLoggerListener = new PrintCommandToLoggerListener(myLog); PrintWriter writer = myPrintCommandToLoggerListener.getPrintWriter(); myFtp.addProtocolCommandListener(new PrintCommandListener(writer, true, '\n', true)); // myFtp.addProtocolCommandListener( new PrintCommandListener( new PrintWriter( System.out ), true ) ); try { int reply; if (myPort > 0) { myLog.debug("Connecting to " + myServer + ":" + myPort); myFtp.connect(myServer, myPort); } else { myLog.debug("Connecting to " + myServer + " (default port)"); myFtp.connect(myServer); } myLog.debug("Connected to " + myServer + " on " + (myPort > 0 ? myPort : myFtp.getDefaultPort())); // After connection attempt, you should check the reply code to verify success. reply = myFtp.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { myFtp.disconnect(); throw createFtpException("FTP server refused connection."); } } catch (IOException e) { throw createFtpException("Could not connect to server.", e); } if (!myFtp.login(myUsername, myPassword)) { myFtp.logout(); throw createFtpException("Problems on login"); } myLog.debug("Remote system is " + myFtp.getSystemType()); myFtp.setFileType(FTP.BINARY_FILE_TYPE); // Use passive mode as default because most of us are behind firewalls these days. if (myLocalActive) { myFtp.enterLocalActiveMode(); } else { myFtp.enterLocalPassiveMode(); } myFtp.setUseEPSVwithIPv4(myUseEpsvWithIPv4); } finally { myPrintCommandToLoggerListener.flushRest(); } } /** * @throws SdiException * @throws FileNotFoundException * @throws IOException */ public void uploadFiles(Map<String, InputStream> aFilesToUpload) throws SdiException { for (String target : aFilesToUpload.keySet()) { try { myLog.debug("Uploading a file to " + target); InputStream is = aFilesToUpload.get(target); if (is == null) { throw new SdiException("InputStream for target file " + target + " is null", SdiException.EXIT_CODE_UNKNOWN_ERROR); } // if is == null myFtp.storeFile(target, is); myFtp.noop(); // check that control connection is working OK } catch (FTPConnectionClosedException e) { throw createFtpException("Server closed connection.", e); } catch (IOException e) { throw createFtpException("IOException caught", e); } finally { myPrintCommandToLoggerListener.flushRest(); } } } /** * @throws SdiException */ public void logoutAndDisconnect() throws SdiException { if (myFtp == null) { myLog.debug("FTP not yet initilized. No logout"); return; } // if myFtp == null if (!myFtp.isConnected()) { myLog.debug("FTP not connected. No logout"); return; } try { myFtp.logout(); } catch (FTPConnectionClosedException e) { myLog.warn("Problems FTP logout: " + e.getMessage()); } catch (IOException e) { myLog.warn("Problems FTP logout: " + e.getMessage()); } finally { if (myFtp.isConnected()) { try { myFtp.disconnect(); } catch (IOException f) { myLog.warn("Problems FTP disconnect: " + f.getMessage()); } } } } private SdiException createFtpException(String aMessage) { return createFtpException(aMessage, null); } private SdiException createFtpException(String aMessage, Throwable aThrowable) { StringBuilder sb = new StringBuilder(aMessage); if (myFtp != null) { sb.append("ReplyCode: ").append(myFtp.getReplyCode()); sb.append("\n Reply-Message:"); sb.append("\n ").append( StringUtils.collectionToDelimitedString(Arrays.asList(myFtp.getReplyStrings()), "\n ")); } // if myFtp != null return new SdiException(sb.toString(), aThrowable, SdiException.EXIT_CODE_FTP_ERROR); } }