/******************************************************************************
 * Description: FTP Handler
 *
 * Modification Log
 * Ver   Date          Author                Description
 * ----------------------------------------------------------------------------
 * 0.0a  Jan 09, 2004  Anoop Johnson         Created
 *****************************************************************************/

import org.apache.commons.net.ftp.*;

import java.io.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * FTP Handler. Provides various methods to perform various file operations
 * on a file server.
 * @author Anoop Johnson
 * @version 0.0a
 */
public class FTPHandler {
    private static final int DEFAULT_PORT = 21;
    private static final String PROTOCOL = "ftp";
    private static final String INFO_SEPARATOR = ":";
    private static final String NULL_STRING = "";
    private static final String PATH_SEPARATOR = "/";

    /**
     * Stores the singleton instance of the FTPHandler
     */
    private static FTPHandler ftpHandler = new FTPHandler();

    /**
     * Holds a FTP connection map for each host, port, user and password.
     */
    private Map ftpMaps = new HashMap();

    protected FTPHandler() {
    }

    /**
     * Returns the singleton instance of the FTP Handler.
     * @return Instance of FTP handler.
     */
    public static FTPHandler getInstance() {
        return ftpHandler;
    }

    /**
     * Search and return a free connection from the connection pool.
     * If no free connection is found, create a new connection and add
     * to the pool.
     * @param hostName Host name
     * @param port Port address
     * @param userName User name
     * @param password Password
     * @return Instance of <code>FTPClient</code>
     * @throws DBException db-Valuations Exception
     */
    private synchronized FTPClient getConnection(String hostName, int port,
        String userName, String password) throws Exception {
        HashMap ftpMap = null;
        String mapKey = null;
        Set mapKeys = null;
        Iterator iterKeys = null;
        FTPClient ftpClient = null;
        Boolean avail = null;
        ArrayList closedConnections = null;

        try {
            System.out.println("Inside getConnection");

            //If no port address is specified, take the default port address (21)
            if (port == -1) {
                port = DEFAULT_PORT;
            }

            //Create a list to store the connections that are eligible for garbage
            //collection
            closedConnections = new ArrayList();

            //Create the key to identify the map of connections for a given host,
            //port, user and password
            mapKey = hostName + port + userName + password;

            System.out.println("Key: " + mapKey);

            ////////////////////////////////////////////////////////
            //synchronized (ftpMaps) {
            ////////////////////////////////////////////////////////
            //Search and get the map of connection for the given host, port, user
            //and password in the Map of connection maps
            ftpMap = (HashMap) ftpMaps.get(mapKey);

            System.out.println("Main Map: " + ftpMaps);

            System.out.println("Connection Map: " + ftpMap);

            //If a connection map is found
            if (ftpMap != null) {
                //Get the list of keys from the connection map
                mapKeys = ftpMap.keySet();
                iterKeys = mapKeys.iterator();

                //While there are keys in the connection map
                while (iterKeys.hasNext()) {
                    //Get the next key from the list of keys
                    ftpClient = (FTPClient) iterKeys.next();

                    //Get the connection corresponding to the key from the connection
                    //map
                    avail = (Boolean) ftpMap.get(ftpClient);

                    //If the connection is free
                    if (avail.booleanValue() == true) {
                        System.out.println("Free connection found");

                        //Check if the connection is still connected
                        if (!ftpClient.isConnected()) {
                            System.out.println("Is not connected");

                            //If the connection has got disconnected (due to connection
                            //timeout etc.), disconnect the connection and remove the
                            //connection from the hash map.
                            ftpClient.disconnect();
                            closedConnections.add(ftpClient);
                        } else {
                            //If the connection is still connected 
                            try {
                                //Confirm if the connection is still connected.
                                ftpClient.noop();
                            } catch (FTPConnectionClosedException e) {
                                //If the connection closed without indication
                                //logger.debug(

                                //Remove the connection from the map
                                closedConnections.add(ftpClient);

                                continue;
                            } catch (Exception e) {
                                //If the connection has got disconnected without indication,
                                //remove the connection from the hash map
                                //logger.debug(
                                closedConnections.add(ftpClient);

                                continue;
                            }

                            //Got an active and free connection, put the connection in the
                            //hashmap and mark as busy.
                            ftpMap.put(ftpClient, new Boolean(false));

                            //Return the connection
                            return ftpClient;
                        }
                    }
                }

                //Initiate the garbage collection
                collectGarbage(ftpMap, closedConnections);

                //If no connection is found in the hash map, create a new connection 
                ftpClient =
                    createConnection(hostName, port, userName, password);

                //Put the connection in the hashmap and mark as busy.
                ftpMap.put(ftpClient, new Boolean(false));

                //Return the connection
                return ftpClient;
            }

            //If no connection map is found create a new map to hold the connections
            ftpMap = new HashMap();

            //Create a new connection.
            ftpClient = createConnection(hostName, port, userName, password);

            //Put the connection in the map.
            ftpMap.put(ftpClient, new Boolean(false));

            //Put the connection map in the map. 
            ftpMaps.put(mapKey, ftpMap);

            //////////////////////////////
            //}
            //////////////////////////////

            //Return the connection
            //ftpClient.enterLocalActiveMode();
            return ftpClient;
        } catch (FTPConnectionClosedException e) {
            //logger.debug(e, e);

            if (ftpClient != null) {
                if (ftpClient.isConnected()) {
                    try {
                        ftpClient.disconnect();
                    } catch (IOException ioexception) {
                        //Don't do anything
                    }
                }
            }

        } catch (IOException e) {
            //logger.debug(e, e);

            if (ftpClient != null) {
                if (ftpClient.isConnected()) {
                    try {
                        ftpClient.disconnect();
                    } catch (IOException ioexception) {
                        //Don't do anything
                    }
                }
            }

        } catch (Exception e) {
            //logger.debug(e, e);

            if (ftpClient != null) {
                if (ftpClient.isConnected()) {
                    try {
                        ftpClient.disconnect();
                    } catch (IOException ioexception) {
                        //Don't do anything
                    }
                }
            }

        }
        return null;
    }

    /**
     * This method releases the inactive connections in the connections map,
     * that have been closed by the server
     * @param ftpMap FTP Connection Map
     * @param closedConnections Closed connections
     */
    public void collectGarbage(Map ftpMap, List closedConnections) {
        FTPClient closedConnection = null;
        int closedConnectionsCount = 0;
        int i = 0;

        try {
            //For each connection in the list
            closedConnectionsCount = closedConnections.size();

            for (i = 0; i < closedConnectionsCount; i++) {
                //Get the inactive connection from the list
                closedConnection = (FTPClient) closedConnections.get(i);

                //Remove the connection from the FTP Connection Map
                ftpMap.remove(closedConnection);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Creates and returns a new connection for the given hostName, port,
     * userName and password.
     * @param hostName Host name
     * @param port Port address
     * @param userName User name
     * @param password Password
     * @return Connection
     * @throws ExceptionDB Exception
     */
    public FTPClient createConnection(
        String hostName,
        int port,
        String userName,
        String password)
        throws Exception {
        FTPClient ftpClient = null; //Holds the FTP connection

        try {
            System.out.println("Inside createConnection");

            //Create a new instance fo te FTP Client class
            ftpClient = new FTPClient();

            //Connect to the server using the given userName nad password
            ftpClient.connect(hostName, port);

            //Cross check if it got connected properly
            int reply = ftpClient.getReplyCode();

            if (!FTPReply.isPositiveCompletion(reply)) {
                //If the connection is not properly setup, disconnect and raise an
                //exception.	
                ftpClient.disconnect();

            }

            //Login into the FTP server using given username and password
            ftpClient.login(userName, password);

            //Set the file type as binary
            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            
                    } catch (FTPConnectionClosedException e) {
            //logger.debug(e, e);

            if (ftpClient != null) {
                if (ftpClient.isConnected()) {
                    try {
                        ftpClient.disconnect();
                    } catch (IOException ioexception) {
                        //Don't do anything
                    }
                }
            }

        } catch (IOException e) {
            //logger.debug(e, e);

            if (ftpClient != null) {
                if (ftpClient.isConnected()) {
                    try {
                        ftpClient.disconnect();
                    } catch (IOException ioexception) {
                        //Don't do anything
                    }
                }
            }

        } catch (Exception e) {
            throw e;
        }

        return ftpClient;
    }

    /**
     * Gets an returns a list of files in the given directory on the FTP server.
     * @param path Path of the directory
     * @return List of filenames
     * @throws ExceptionDB Exception
     */
    public List dirFiles(String path) throws Exception {
        //Stores the information of files stored in a directory on the FTP server
        FTPFile[] ftpFiles = (FTPFile[]) null;
        FTPFile ftpFile = null; //Holds information of a file on FTP server
        int ftpFilesCount = 0;
        int i = 0;
        String fileName = null;
        ArrayList fileNames = null;
        FTPUrl dbUrl = null;
        FTPClient ftpClient = null; //FTP connection

        try {
            fileNames = new ArrayList();

            //Create a DBUrl object for the path of the directory
            dbUrl = new FTPUrl(path);

            //Get a connection using the host, port, username and password given in
            //the directory path 
            ftpClient =
                getConnection(
                    dbUrl.getHost(),
                    dbUrl.getPort(),
                    getUserName(dbUrl.getUserInfo()),
                    getPassword(dbUrl.getUserInfo()));
            System.out.println("Hostname: " + dbUrl.getHost());
            System.out.println("Port: " + dbUrl.getPort());
            System.out.println("Username" + getUserName(dbUrl.getUserInfo()));
            System.out.println("Password" + getPassword(dbUrl.getUserInfo()));

            //Remove the first '/' character from the given relative path
            //Get the list of files for the given relative path
            ftpFiles = ftpClient.listFiles(dbUrl.getPath().substring(1));
            System.out.println("Inside list: Reply is " + ftpClient.getReplyString());

            System.out.println(
                "ftpClient.isConnected() : " + ftpClient.isConnected());
            //If there are no files in the directory, return the blank list of files
            if (ftpFiles == null) {
                putConnection(ftpClient);
                return fileNames;
            }

            //For each file in the list
            ftpFilesCount = ftpFiles.length;

            for (i = 0; i < ftpFilesCount; i++) {
                //Get the file object
                ftpFile = ftpFiles[i];

                if (ftpFile.getType() == FTPFile.FILE_TYPE) {
                    //Get the file name
                    fileName = ftpFile.getName();

                    //Add the filename to the list
                    fileNames.add(fileName);
                }
            }

            //Release the connection
            putConnection(ftpClient);
        } catch (FTPConnectionClosedException e) {
	    System.out.println("Inside FTPConnectionClosedException e");
	    e.printStackTrace();
            putConnection(ftpClient);
        } catch (IOException e) {
	    e.printStackTrace();
            putConnection(ftpClient);
        } catch (Exception e) {
	    e.printStackTrace();
            putConnection(ftpClient);
        }

        //Return list of file names
        return fileNames;
    }

    /**
     * Gets an returns a list of files in the given directory on the FTP server.
     * @param path Path of the directory
     * @return List of filenames
     * @throws ExceptionDB Exception
     */
    private boolean exists(String path) throws Exception {
        int ftpFilesCount = 0;
        int i = 0;
        String fileName = null;
        String[] names = null;
        ArrayList fileNames = null;
        FTPUrl dbUrl = null;
        FTPClient ftpClient = null; //FTP connection

        boolean found = false;

        try {
            fileNames = new ArrayList();

            //Create a DBUrl object for the path of the directory
            dbUrl = new FTPUrl(path);

            //Get a connection using the host, port, username and password given in
            //the directory path 
            ftpClient =
                getConnection(
                    dbUrl.getHost(),
                    dbUrl.getPort(),
                    getUserName(dbUrl.getUserInfo()),
                    getPassword(dbUrl.getUserInfo()));

            //Remove the first '/' character from the given relative path
            //Get the list of files for the given relative path
            names = ftpClient.listNames(dbUrl.getPath().substring(1));

            //If there are no files in the directory, return false
            if (names == null) {
                //Release the connection
                putConnection(ftpClient);

                return false;
            }

            //For each file in the list
            ftpFilesCount = names.length;

            if (ftpFilesCount > 0) {
                found = true;
            } else {
                found = false;
            }

            //Release the connection
            putConnection(ftpClient);
        } catch (FTPConnectionClosedException e) {
            //logger.debug(e, e);
            putConnection(ftpClient);

        } catch (IOException e) {
            //logger.debug(e, e);
            putConnection(ftpClient);

        } catch (Exception e) {
            //logger.debug(e, e);
            putConnection(ftpClient);

        }

        if (found) {
            //logger.debug("File found: " + path);
        } else {
            //logger.debug("File not found: " + path);
        }

        return found;
    }

    /**
     * Fetches a file with the given path and filename from the FTP server and
     * returns the contents of the file as an <code>InputStream</code>.
     * @param filePath Path of the file
     * @return Input stream
     * @throws ExceptionDB Exception
     */
    public InputStream getFile(String filePath) throws Exception {
        //Input stream to store the contents of the file.
        InputStream inputStream = null;
        FTPUrl dbUrl = null; //dbUrl object for the file path.

        //Output stream to store the contents of the file.
        ByteArrayOutputStream boStream = null;

        FTPClient ftpClient = null; //Holds the FTP connection
        int i = 0;
        int fileNamesCount = 0;

        List fileNames = null;

        //I//logger //logger = null;

        try {
            //If the file does not exist, raise an exception      
            if (!exists(filePath)) {
            }

            //Create a DBUrl object for the given file path
            dbUrl = new FTPUrl(filePath);

            //Get a connection using the host, port, username and password given in
            //the directory path 
            ftpClient =
                getConnection(
                    dbUrl.getHost(),
                    dbUrl.getPort(),
                    getUserName(dbUrl.getUserInfo()),
                    getPassword(dbUrl.getUserInfo()));

            //Create a byte array output stream
            boStream = new ByteArrayOutputStream();

            //Remove the first '/' character from the given relative path
            //Get the contents of the file with the given relative path,
            //and store it in the given byte array output stream
            ftpClient.retrieveFile(dbUrl.getPath().substring(1), boStream);

            //Get the contents of the byte array output stream as a byte array
            //and create a new input stream
            inputStream = new ByteArrayInputStream(boStream.toByteArray());

            //Close the connection
            putConnection(ftpClient);
        } catch (FTPConnectionClosedException e) {
            //logger.debug(e, e);
            putConnection(ftpClient);

        } catch (IOException e) {
            //logger.debug(e, e);
            putConnection(ftpClient);

        } catch (Exception e) {
            //logger.debug(e, e);
            putConnection(ftpClient);

        }

        //Return the input stream
        return inputStream;
    }

    /**
     * Stores the given file on the FTP server in the given directory with the
     * given name
     * @param inputStream <code>InputStream</code> containing the contents of the
     * file.
     * @param filePath The path where to store the file on the FTP server
     * @throws ExceptionDB Exception
     */
    public String putFile(InputStream inputStream, String filePath)
        throws Exception {
        FTPUrl dbUrl = null;
        String reply = null;
        int ch = 0;

        FTPClient ftpClient = null; //Holds the FTP connection

        try {
            //Create a DBUrl object for the given file path
            dbUrl = new FTPUrl(filePath);

            //Get a connection using the host, port, username and password given in
            //the directory path 
            ftpClient =
                getConnection(
                    dbUrl.getHost(),
                    dbUrl.getPort(),
                    getUserName(dbUrl.getUserInfo()),
                    getPassword(dbUrl.getUserInfo()));

            //Remove the first '/' character from the given relative path
            //Put the contents of the file at the given relative path,
            //with the given name.
            ftpClient.storeFile(dbUrl.getPath().substring(1), inputStream);

            reply = ftpClient.getReplyString();
            //Close the input stream
            inputStream.close();

            //Close the FTP connection
            putConnection(ftpClient);
        } catch (Exception e) {
            e.printStackTrace();
            putConnection(ftpClient);
        }

        return reply;
    }

    /**
     * Deletes the file with the given path from the FTP server.
     * @param filePath Path of the file
     * @throws ExceptionDB Exception
     */
    public void del(String filePath) throws Exception {
        FTPUrl dbUrl = null;
        //I//logger //logger = null;
        FTPClient ftpClient = null; //Holds the FTP connection

        try {

            //Create a DBUrl object for the given file path
            dbUrl = new FTPUrl(filePath);

            //Get a connection using the host, port, username and password given in
            //the directory path 
            ftpClient =
                getConnection(
                    dbUrl.getHost(),
                    dbUrl.getPort(),
                    getUserName(dbUrl.getUserInfo()),
                    getPassword(dbUrl.getUserInfo()));

            //Delete the file with the given path from the FTP server
            ftpClient.deleteFile(dbUrl.getPath().substring(1));

            //Close the connection
            putConnection(ftpClient);
        } catch (FTPConnectionClosedException e) {
            putConnection(ftpClient);

        } catch (IOException e) {
            putConnection(ftpClient);

        } catch (Exception e) {
            putConnection(ftpClient);
        }
    }

    /**
     * Extracts the username from the user info part of the URL
     * @param userInfo User info
     * @return User name
     */
    private String getUserName(String userInfo) {
        //Extract the substring from the first character of user info till the
        //character before the separator ':', e.g. aman_mishra in aman_mishra:abc
        return userInfo.substring(0, userInfo.indexOf(INFO_SEPARATOR));
    }

    /**
     * Extracts the password part from the user info part of the URL
     * @param userInfo User info
     * @return User name
     */
    private String getPassword(String userInfo) {
        //Extract the substring from the character just after the separator ':'
        //till the last character of user info, e.g. abc in aman_mishra:abc
        return userInfo.substring(
            userInfo.indexOf(INFO_SEPARATOR) + 1,
            userInfo.length());
    }

    /**
     * Returns the connection to the hash map and marks the status as free.
     * @param ftpClient FTP connection
     * @throws ExceptionDB Exception
     */
    private synchronized void putConnection(FTPClient ftpClient)
        throws Exception {
        int i;
        HashMap ftpMap = null; //Holds the map of connections
        Set mapKeys = null; //List of keys from the map of connection maps
        Iterator iterKeys = null;
        String mapKey = null;
        Boolean avail = null;

        try {
            mapKeys = ftpMaps.keySet();
            iterKeys = mapKeys.iterator();

            //For each map of connections in the map
            while (iterKeys.hasNext()) {
                mapKey = (String) iterKeys.next();

                //Get the connection map corresponding to the key
                ftpMap = (HashMap) ftpMaps.get(mapKey);

                //Find if the connection is present in the map
                avail = (Boolean) ftpMap.get(ftpClient);

                //If the connection is found in the connections map
                if (avail != null) {
                    //Check if the connection is still connected
                    if (!ftpClient.isConnected()) {
                        //If the connection is not connected, remove the connection from
                        //the map
                        ftpMap.remove(ftpClient);

                    } else {
                        //If the connection is still connected
                        try {
                            //Confirm if the connection is still connected
                            ftpClient.noop();
                        } catch (FTPConnectionClosedException e) {
                            //If the connection closed without indication
                            //Remove the connection from the map
                            ftpMap.remove(ftpClient);

                            return;
                        } catch (Exception e) {
                            //If the connection closed without indication
                            //Remove the connection from the map
                            ftpMap.remove(ftpClient);

                            return;
                        }

                        //Put the connection in the map with the status as free
                        ftpMap.put(ftpClient, new Boolean(true));

                        return;
                    }
                }
            }
        } catch (Exception e) {
        }
    }

    public static void main(String[] args) throws Exception {
        String filename = "c:/test.pdf";
        System.out.println("The file name is " + filename);
        FTPHandler hand = FTPHandler.getInstance();
        
        System.out.println("READING FILE");
        FileInputStream fis = new FileInputStream(filename);
        System.out.println("BEFORE PUTTING");
        
        String reply = hand.putFile(fis,
                                     "ftp://username:password@ftp.server.com/" + 
                                     "test.pdf");
        System.out.println("FINISHED PUTTING and reply is : " + reply);

        System.out.println("READING FILE");

        List lst = hand.dirFiles("ftp://username:password@ftp.server.com/*");
	
        System.out.println(lst.size());
        for (int i = 0; i < lst.size(); i++) {
            System.out.println(lst.get(i));
        }
    }
}