/*
ESUP-portail is a french academic project developed under the GPL (General Public License) augmented according to the following :
A binary or source file developped by ESUP-portail can be used or compiled with products under Apache license.
The official english text of the GPL can be found here : http://www.gnu.org/licenses/gpl.html .
A non official french translation can be found here : http://www.linux-France.org/article/these/gpl.html .
The different kinds of licenses governing the products developed by the Apache foundation can be found here : http://www.apache.org/licenses .
It follows that you can as well as use download contents as well modify and redistribute them provided you respect the GPL terms.
Downloading and using such contents do not provide any guaranty.
Be sure that you have well understood the terms of the license before using the contents it covers.
The ESUP-portail distribution includes the following distributions :
    * UPortal :
      software developed by JA-SIG (Java Architecture - Special Interest Group)
      You can find the license page here : http://mis105.udel.edu/ja-sig/uportal/license.html
    * CAS :
      SSO solution developed by Yale University
      You can find the project page here : http://www.yale.edu/tp/auth
    * Cocoon :
      XML framework distributed by the Apache foundation under Apache license;
      Please find the full text here : http://cocoon.apache.org/2.1/license.html
    * Mod_dav:
      A DAV module for Apache web server
      You can find the project page here : http://www.webdav.org/mod_dav
    * IMP :
      webmail from Horde application framework
      You can find the project page here : http://www.horde.org
    * �. To be completed
*/

package org.esupportail.portal.channels.CStockage.provider.access;
 
import java.io.IOException; 
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector; 


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.esupportail.portal.channels.CStockage.provider.ChannelResource;
import org.esupportail.portal.channels.CStockage.provider.ResourceControl;
import org.esupportail.portal.channels.CStockage.config.Space;
import org.esupportail.portal.channels.CStockage.exception.AclAccessException;
import org.esupportail.portal.channels.CStockage.exception.AclReadException;
import org.esupportail.portal.channels.CStockage.exception.AclWriteException;
import org.esupportail.portal.channels.CStockage.exception.ApplicationException;
import org.esupportail.portal.channels.CStockage.exception.BadConnexionParameters;
import org.esupportail.portal.channels.CStockage.exception.BadFormatException;
import org.esupportail.portal.channels.CStockage.exception.CopyException;
import org.esupportail.portal.channels.CStockage.exception.CreateDirectoryException;
import org.esupportail.portal.channels.CStockage.exception.DeleteException;
import org.esupportail.portal.channels.CStockage.exception.DownloadException;
import org.esupportail.portal.channels.CStockage.exception.EmptyFileOnUploadException;
import org.esupportail.portal.channels.CStockage.exception.MoveException;
import org.esupportail.portal.channels.CStockage.exception.NotAuthorizedDeleteException;
import org.esupportail.portal.channels.CStockage.exception.NotAuthorizedException;
import org.esupportail.portal.channels.CStockage.exception.NotAuthorizedNewDirException;
import org.esupportail.portal.channels.CStockage.exception.NotAuthorizedRenameException;
import org.esupportail.portal.channels.CStockage.exception.NotAuthorizedUploadException;
import org.esupportail.portal.channels.CStockage.exception.NotExistsResourceException;
import org.esupportail.portal.channels.CStockage.exception.NotSupportedAclException;
import org.esupportail.portal.channels.CStockage.exception.OverQuotaException;
import org.esupportail.portal.channels.CStockage.exception.PasteDeletedResourceException;
import org.esupportail.portal.channels.CStockage.exception.PasteInChildDirectoryException;
import org.esupportail.portal.channels.CStockage.exception.PasteNotAuthorizedResourceException;
import org.esupportail.portal.channels.CStockage.exception.PasteWithSameNameException;
import org.esupportail.portal.channels.CStockage.exception.PropertiesException;
import org.esupportail.portal.channels.CStockage.exception.RenameException;
import org.esupportail.portal.channels.CStockage.exception.ServerException;
import org.esupportail.portal.channels.CStockage.exception.StillExistsException;
import org.esupportail.portal.channels.CStockage.exception.UploadException;
import org.esupportail.portal.channels.CStockage.exception.ChannelException;
import org.esupportail.portal.utils.webdav.acl.EsupPermissions;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPClientConfig;

/**
 * Id: FtpAccessImpl.java,v 1.0 14 avr. 2004<br/>
 * Copyright (c) 2005 Esup Portail (www.esup-portail.org)<br/>
 * Classes: FtpAccessImpl<br/>
 * Original Author: Yohan COLMAN<br/>
 * Original Author: Olivier FRANCO<br/>
 * <p>Implements the ServerAccess methods to access an FTP server</p>
 * <h3>Configuration file parameters</h3>
 * <pre>
 * &lt;SERVER url="ftp://my.ftp.server
 *          serverType="ftp"
 *          authenticationMode="cas|asked|trusted"
 *          manageAcl="false">
 *          &lt;PARAMETER name="ftpControlEncoding" value="UTF-8"/&gt;
 *          &lt;PARAMETER name="ftpUsePasvMode" value="true"/&gt;
 *          &lt;PARAMETER name="ftpType" value="UNIX"/&gt;
 *          &lt;PARAMETER name="ftpFileType" value="BINARY"/&gt;
 *          &lt;PARAMETER name="ftpBufferSize" value="16384"/&gt;
 *          &lt;PARAMETER name="ftpCacheCreds" value="true"/&gt;
 *  &lt;/SERVER&gt;
 *  </pre>
 * <ul>
 * 	<li><strong>ftpControlEncoding</strong> : use the given charset to communicate with ftp server. 
 * 		Valid values are any charset your JVM can understand (ASCII, UTF-8, ISO-8859-1 ...).
 * 		Default to <strong>JVM default charset</strong> 
 * 	</li>
 * 	<li><strong>ftpUsePasvMode</strong> : use ftp passiv mode [ true | false ] default to <strong>true</strong></li>
 * 	<li><strong>ftpType</strong> : ftp server type :
 * 		<ul>
 * 			<li><strong>UNIX</strong> : unix like operating system</li>
 * 			<li><strong>MVS</strong> : <a href="http://en.wikipedia.org/wiki/MVS">MVS operating system</a></li>
 * 			<li><strong>NT</strong> : MS windows operating system</li>
 * 			<li><strong>OS2</strong> : IBM OS/2 operationg system</li>
 * 			<li><strong>OS400</strong> : IBM OS/400 operating system</li>
 * 			<li><strong>VMS</strong> : <a href="http://en.wikipedia.org/wiki/OpenVMS">VMS operating system</a></li>
 * 		</ul>
 * 		default to <strong>UNIX</strong>
 *  </li>
 * 	<li><strong>ftpFileType</strong> : ftp transfert type :
 * 		<ul>
 * 			<li><strong>BINARY</strong> : transfert in binary form</li>
 * 			<li><strong>ASCII</strong> : transfert in ascii form</li>
 * 		</ul>
 * 		default to <strong>BINARY</strong>
 *  </li>
 *  <li><strong>ftpBufferSize</strong> : size of the internal ftp buffer; default to <strong>16384</strong></li>
 *  <li><strong>ftpCacheCreds</strong> : if <strong>true</strong> attempt to reuse <em>CAS</em> credential. Default to <strong>false</strong></li>
 * </ul>
 *  
 */
public class FtpAccessImpl extends ServerAccess {

	/**
	 * Logger object
	 */
	protected static final Log log = LogFactory.getLog(FtpAccessImpl.class);

	// FTP client parameter (should be configured from the CStockage configuration file)
	/**
	 * default client (ie the portal) encoding
	 */
	protected String ftpControlEncoding = "UTF-8";
	/**
	 * use passive mode
	 */
	protected boolean ftpUsePasvMode = true;
	/**
	 * ftp client type
	 */
	protected String ftpType = FTPClientConfig.SYST_UNIX; 
	/**
	 * ftp buffer size
	 */
	protected int ftpBufferSize = 16384;
	/**
	 *
	 */
	protected int ftpFileType = FTP.BINARY_FILE_TYPE;
	
	/**
	 * use authentication cache
	 */
	protected boolean useAuthCache = false;
	
	/**
	 * The user login
	 */
	private String login;
	
	/**
	 * The password for the dav access
	 */
	private String password;

	/**
	 * user is authenticated
	 */	
	private boolean authenticated = false;

	/**
	 * the space this object is binded to
	 */
	protected Space space = null;

	/**
	 * The Http url used to access the webdav
	 */
	private String host;	
	
	/**
	 * The FTP Client
	 */
	private FTPClient ftp = null;

	/**
     	 * Default constructor
	 * for the moment we could use it to override ftp client
	 * settings ans authentication mode 
     	 */
	public FtpAccessImpl() {}
	
	/**
	 * This method initializes the parameters. You have to call this method before calling connect method.
	 * @param space the space corresponding to the server access
	 * @throws MalformedURLException
	 * @throws PropertiesException
	 */
	public void init(Space space) throws MalformedURLException, PropertiesException {
		this.space = space;
		this.host = space.getServer().getUrl();	
		if (this.host.endsWith("/")) {
			this.host = this.host.substring(0, this.host.length()-1);
	    	}
	    	if (this.host.startsWith("/")) {
	    		this.host = this.host.substring(1);
	    	}
		// strip ftp://
		if ( this.host.startsWith("ftp://") ) {
			this.host = this.host.substring(6);
		}
		// initialize ftp client parameters
		
		// parameters from configuration file
		// the client charset
		String ftpControlEncodingParameter = this.space.getParameter("ftpControlEncoding");
		// check for charset or die with PropertiesException
		if (ftpControlEncodingParameter!=null) {
			boolean charset_supported = false;
			try {
				charset_supported = Charset.isSupported(ftpControlEncodingParameter);
			} catch (IllegalCharsetNameException e) {
				charset_supported = false;
			}
			if ( ! charset_supported ) {
				PropertiesException e = new PropertiesException();
				e.initCause(new Throwable("ftpControlEncoding invalid value : "+ftpControlEncodingParameter));
				throw e;
			}
			this.ftpControlEncoding = ftpControlEncodingParameter;
		} else {
			// what is the default ! no default !
			this.ftpControlEncoding = null;
		}
		
		// check for passive mode; default to true
		String ftpUsePasvModeParameter = this.space.getParameter("ftpUsePasvMode");
		if (ftpUsePasvModeParameter!=null) {
			if (ftpUsePasvModeParameter.equals("true")) {
				this.ftpUsePasvMode = true;
			}
			else if (ftpUsePasvModeParameter.equals("false")) {
				this.ftpUsePasvMode = false;
			} else {
				PropertiesException e = new PropertiesException();
				e.initCause(new Throwable("ftpUsePasvMode invalid value : "+ftpUsePasvModeParameter));
				throw e;
			}
		} else {
			log.info("FtpAccessImpl::init() properties \"ftpUsePasvMode\" not found using default : true");
			this.ftpUsePasvMode = true;
		}
		
		// check for ftp type
		String ftpTypeParameter = this.space.getParameter("ftpType");
		if (ftpTypeParameter!=null) {
			if (ftpTypeParameter.equals("UNIX")) {
				this.ftpType = FTPClientConfig.SYST_UNIX;
			}
			else if (ftpTypeParameter.equals("MVS")) {
				this.ftpType = FTPClientConfig.SYST_MVS;
			}
			else if (ftpTypeParameter.equals("NT")) {
				this.ftpType = FTPClientConfig.SYST_NT;
			}
			else if (ftpTypeParameter.equals("OS2")) {
				this.ftpType = FTPClientConfig.SYST_OS2;
			}
			else if (ftpTypeParameter.equals("OS400")) {
				this.ftpType = FTPClientConfig.SYST_OS400;
			}
			else if (ftpTypeParameter.equals("VMS")) {
				this.ftpType = FTPClientConfig.SYST_VMS;
			} else {
				PropertiesException e = new PropertiesException();
				e.initCause(new Throwable("ftpType invalid value : "+ftpTypeParameter));
				throw e;
			}
		} else {
			log.info("FtpAccessImpl::init() properties \"ftpType\" not found using default : UNIX");
			this.ftpType = FTPClientConfig.SYST_UNIX;
		}
		
		// check for transfert type
		String ftpFileTypeParameter = this.space.getParameter("ftpFileType");
		if (ftpFileTypeParameter!=null) {
			if (ftpFileTypeParameter.equals("BINARY")) {
				this.ftpFileType = FTP.BINARY_FILE_TYPE;
			}
			else if (ftpFileTypeParameter.equals("ASCII")) {
				this.ftpFileType = FTP.ASCII_FILE_TYPE;
			} else {
				PropertiesException e = new PropertiesException();
				e.initCause(new Throwable("ftpFileType invalid value : "+ftpFileTypeParameter));
				throw e;
			}
			/* see : 
			 * http://jakarta.apache.org/commons/net/api/org/apache/commons/net/ftp/FTPClient.html
			 * 
			 * EBCDIC transfert is not supported
			 * IMAGE == BINARY
			 * 
			 * 
			else if (ftpFileTypeParameter.equals("EBCDIC")) {
				this.ftpFileType = FTP.EBCDIC_FILE_TYPE;
			}
			else if (ftpFileTypeParameter.equals("IMAGE")) {
				this.ftpFileType = FTP.IMAGE_FILE_TYPE;
			}
			else if (ftpFileTypeParameter.equals("LOCAL")) {
				this.ftpFileType = FTP.LOCAL_FILE_TYPE;
			} else {
				PropertiesException e = new PropertiesException();
				e.initCause(new Throwable("ftpFileType invalid value : "+ftpFileTypeParameter));
				throw e;
			}*/
		} else {
			log.info("FtpAccessImpl::init() properties \"ftpFileType\" not found using default : BINARY");
			this.ftpFileType = FTP.BINARY_FILE_TYPE;
		}
		
		// check for buffer size
		String ftpBufferSizeParameter = this.space.getParameter("ftpBufferSize");
		if ( ftpBufferSizeParameter != null ) {
			int bsize = 0;
			boolean bsize_error = false;
			try {
				bsize = Integer.parseInt(ftpBufferSizeParameter);
			} catch (NumberFormatException e) {
				bsize_error = true;
			}
			if ( bsize <= 0 || bsize_error ) {
				PropertiesException ex = new PropertiesException();
				ex.initCause(new Throwable("ftpBufferSize invalid value : "+ftpBufferSizeParameter));
				throw ex;
			}
		} else {
			log.info("FtpAccessImpl::init() properties \"ftpBufferSize\" not found using default : 16384");
			this.ftpBufferSize = 16384;
		}
		
		// check if we need to cache credential
		String ftpCacheCredsParameter = this.space.getParameter("ftpCacheCreds");
		if ( ftpCacheCredsParameter != null ) {
			if ( ftpCacheCredsParameter.equals("true") ) {
				this.useAuthCache = true;
			} else if ( ftpCacheCredsParameter.equals("false") ) {
				this.useAuthCache = false;
			} else {
				PropertiesException ex = new PropertiesException();
				ex.initCause(new Throwable("ftpCacheCreds invalid value : "+ftpCacheCredsParameter));
				throw ex;
			}
		} else {
			log.info("FtpAccessImpl::init() properties \"ftpCacheCreds\" not found using default : false");
			this.useAuthCache = false;
		}
		
//		ftp = initFTPClient();
	}
	
	/**
	 * Initialize FTPClient
	 * 
	 * @return new FTPClient
	 */
	private FTPClient initFTPClient() {
		FTPClient ftp = new FTPClient();
		if ( log.isDebugEnabled() ) 
			log.debug("initFTPClient encoding : "+ftp.getControlEncoding());
		// tel ftp client wich encoding we have
		if ( this.ftpControlEncoding != null )
			ftp.setControlEncoding(this.ftpControlEncoding);
		ftp.setBufferSize(this.ftpBufferSize);
		
		/* FTP Type : should be configured via the CStockage properties file
		 * Type : 
		 * 	SYST_UNIX -> unix like ftp
		 * 	SYST_MVS -> MVS
		 * 	SYST_NT -> Windows NT
		 * 	SYST_OS2 -> OS2
		 * 	SYST_OS400 -> IBM
		 * 	SYST_VMS -> VMS 
		 */
		FTPClientConfig conf = new FTPClientConfig(this.ftpType);
		ftp.configure(conf);
		return ftp;
	}
	/**
	 * Connect this object to the server	 
	 * @throws ServerException
	 * @throws BadConnexionParameters
	 */
	public void connect() throws ServerException, BadConnexionParameters {		
		try {
			if ( log.isDebugEnabled() )	log.debug("FtpAccessImpl::connect() to :"+host);
			// check if client is authenticated
			if (!this.authenticated) {
				// get login and password from the space
				initCredentials(false);
				ftp = initFTPClient();
				ftpConnect();
				// login
				this.authenticated = ftp.login(this.login,this.password);
				// bad authentication
				if (!this.authenticated) {
					// if we use cached authentication then retry
					if (this.useAuthCache) {
						if (log.isDebugEnabled()) log.debug("FtpAccessImpl::connect() using cached PT ? "+this.password);
						initCredentials(true);
//						ftp = initFTPClient();
//						ftpConnect();					
						this.authenticated = ftp.login(this.login,this.password);
					}
				}
				// definitively not authenticated !
				if ( ! this.authenticated ) {
					// invalid credential
					log.error("FtpAccessImpl::connect() unable to login username : "+this.login);
					throw new BadConnexionParameters();
				}
			}
		} catch (IOException ex) {
			ex.printStackTrace();
			log.error("connect IOException :: "+ex, ex);
			throw new ServerException();
		} 
	}

	/**
	 * @throws SocketException
	 * @throws IOException
	 */
	private void ftpConnect() throws SocketException, IOException {
		// do not use thread to avoid "Fatal thread interruption during read" with uPortal
		ftp.setReaderThread(false);
		// if not connected
		ftp.connect(host);
		// set file type
		ftp.setFileType(this.ftpFileType);
		// set passiv mode
		if (this.ftpUsePasvMode)
			ftp.enterLocalPassiveMode();
		else
			ftp.enterLocalActiveMode();
	}
	
	/**
	 * retrieve login and password for this user
	 * enable inheritance to override for cache implementation
	 */
	protected void initCredentials(boolean force) {
		try {
			// login
			if ( this.login == null ) 
				this.login = space.getServer().getLogin();
			
			// password
			// case of CAS : we need a PT for each connection
			if ( this.space.getServer().isCasAuthentication() ) {
				if ( log.isDebugEnabled() ) 
					log.debug("FtpAccessImpl::initCredentials() cas authentication enabled ");
				// if force then retrieve a new cas PT
				if ( force ) {
					this.password = this.space.getCasPt(this.space.getServer().getUrl());
					this.space.getServer().setPassword(this.password);
					if ( log.isDebugEnabled() ) 
						log.debug("FtpAccessImpl::initCredentials() recall PT : "+this.password);
				} else {
					// if we use authentication cache
					if ( this.useAuthCache && this.space.getServer().getPassword() != null ) {
						this.password = this.space.getServer().getPassword();
						if ( log.isDebugEnabled() ) 
							log.debug("FtpAccessImpl::initCredentials() using cached PT : "+this.password);
					} else {
						this.password = this.space.getCasPt(this.space.getServer().getUrl());
						this.space.getServer().setPassword(this.password);
						if ( log.isDebugEnabled() ) 
							log.debug("FtpAccessImpl::initCredentials() new PT : "+this.password);
					}
				}
			} else {
				// no CAS the "ASKED" method
				this.password = this.space.getServer().getPassword();
			}
		} catch (PropertiesException e) {
			log.error("FtpAccessImpl::initCredentials() PropertiesException", e);
		}
	}
	/**
	* Disconnection of the server
	* @throws ServerException
	*/
	public void disconnect() throws ServerException {
		if ( log.isDebugEnabled() ) log.debug("FtpAccessImpl::disconnect() logout");
		if ( ! ftp.isConnected() ) {
			log.debug("FtpAccessImpl::disconnect() not connected ! returning");
			return;
		}
		if ( this.authenticated ) {
			this.authenticated = false;
		}
		try {
			ftp.disconnect();
		} catch (IOException e) {
			log.error("FTPAccessImpl::disconnect() IOException",e);
		}
	}

	/**
	 * Return the stream from a resource
	 * @param fullPath the full resource path
	 * @return the InputStream
	 * @throws IOException
	 */
	public InputStream getMethodData(String fullPath) throws IOException {
		try {
			/* retrieveFileStream must be terminated by
			 * the completePendingCommand
			 * so ....*/
			InputStream is = ftp.retrieveFileStream(fullPath);
			// resource does not exists
			if ( is == null ) return null;
			FtpInputStream fis = new FtpInputStream(ftp,is);
			return fis;
		} catch (IOException ex) {  
			log.error("FtpAccessImpl::getMethodData()"+" :: fullPath="+fullPath,ex);
			throw ex;
		}
	}
	
	
	/**
	 * List all ressources for a given path
	 * @param fullPath directory path on the dav server
	 * @return ChannelWebdavResource array
	 * @throws NotExistsResourceException
	 * @throws ApplicationException
	 * @throws NotAuthorizedException
	 */	
	public ChannelResource[] ls(String fullPath, boolean requiredUportalRealUri) throws NotExistsResourceException, ApplicationException, NotAuthorizedException {

		if (this.ftp == null)
			throw new ApplicationException();
		if ( log.isDebugEnabled() ) log.debug("FtpAccessImpl::ls() fullPath : "+fullPath);
		/* try to connect if needed (require because CStockage Interface can do 
		 * a "ls" without a connect !
		 */
		try {
			if ( ! ftp.isConnected() ) {
				connect();
			}
		} catch (Exception e) {
			log.error("FtpAccessImpl::ls() fullPath="+fullPath,e);
			throw new ApplicationException();
		}
		try {
			// normalize
			fullPath = normalizePath(fullPath);
			// get old directory
			String oldpwd = ftp.printWorkingDirectory();
			// change current directory
			if ( ! ftp.changeWorkingDirectory(fullPath) ) {
				log.error("FtpAccessImpl::ls() unable to change to directory : "+fullPath);
				throw new NotExistsResourceException();
			}
			// list files
			if ( log.isDebugEnabled() )	log.debug("FtpAccessImpl::ls() changeWorkingDirectoy : "+fullPath);
			FTPFile[] files = ftp.listFiles();
			if ( log.isDebugEnabled() ) log.debug("FtpAccessImpl::ls() FTP listFiles :"+files.length);
			// retore old directory
			ftp.changeWorkingDirectory(oldpwd);
			// case empty dir
			if (files.length==0)
				return new ChannelResource[0];

			List<ChannelResource> list =  new ArrayList<ChannelResource>();

			for (int i = 0; i < files.length; i++) {
				FTPFile file = files[i];
				if (file != null && !file.getName().equals(".") && !file.getName().equals("..")) {
					String displayName = file.getName(); 
					String path = fullPath+displayName;
					long contentLength = file.getSize();
					String contentType = getContentTypeFromName(displayName);
					long lastModified = file.getTimestamp().getTimeInMillis();	
					boolean isCollection = file.isDirectory();
					ChannelResource res = new ChannelResource(displayName, displayName, path, contentLength, contentType, lastModified, isCollection, null);
					list.add(res);
				}
			}
			ChannelResource[] channelList = new ChannelResource[list.size()];
			int i = 0;
			for (ChannelResource channelResource : list) {
				channelList[i] = channelResource;
				i++;
			}
			return channelList;
		} catch(IOException e) {
			log.error("FtpAccessImpl::ls() IOException : fullPath="+fullPath,e);
			throw new NotExistsResourceException();
//			} catch (ServerException e) {
//			log.error("FtpAccessImpl::ls() ServerException : fullPath="+fullPath,e);
//			throw new NotExistsResourceException();
//			} catch (BadConnexionParameters e) {
//			log.error("FtpAccessImpl::ls() BadConnexionParameters : fullPath="+fullPath,e);
//			throw new NotExistsResourceException();
		}
	}
	
		
	/**
	 * List all ressources for a given path
	 * @param path directory path on the dav server
	 * @param targetDirectory the directory name to list
	 * @return ChannelWebdavResource array
	 * @throws NotExistsResourceException
	 * @throws ApplicationException
	 * @throws NotAuthorizedException
	 */	
	public ChannelResource[] ls(String path, String targetDirectory, boolean requiredUportalRealUri) throws NotExistsResourceException, ApplicationException, NotAuthorizedException {		
		path = normalizePath(path);
		return ls(path+targetDirectory, requiredUportalRealUri);
	}
	
	
	
	/**
	 * Get the ressource for a given path
	 * @param fullPath directory path on the dav server
	 * @return ChannelResource
	 * @throws NotExistsResourceException
	 * @throws ApplicationException
	 * @throws NotAuthorizedException
	 */	
	public ChannelResource getResource(String fullPath, boolean requiredUportalRealUri) throws NotExistsResourceException, ApplicationException, NotAuthorizedException {
		// not implemented
		return null;
	}
	
	/**
	 * Upload a file to the path given in argument
	 * @param fileName the input file name
	 * @param fileStream the input file stream
	 * @param path the path
	 * @return boolean true if no problem, else false
	 * @throws ApplicationException
	 * @throws ServerException
	 * @throws NotExistsResourceException
	 * @throws StillExistsException
	 * @throws BadFormatException
	 * @throws UploadException
	 * @throws EmptyFileOnUploadException
	 * @throws OverQuotaException
	 * @throws NotAuthorizedDeleteException
	 * @throws NotAuthorizedUploadException
	 * @throws NotAuthorizedException
	 */	
	public boolean upload(String fileName, InputStream fileStream, String path) throws ApplicationException, ServerException, NotExistsResourceException, StillExistsException, BadFormatException, UploadException, EmptyFileOnUploadException,OverQuotaException, NotAuthorizedDeleteException, NotAuthorizedUploadException, NotAuthorizedException, PropertiesException { 
		
		try {
			// the file to upload 
			path = normalizePath(path);
			String cible = path+fileName;                                            
            
			// check if the file still exists 
			boolean exist = false;
			exist = exists(cible);
			if ( exist ) {
        		log.error("FtpAccessImpl::upload () "+fileName+" already exist");
				throw new StillExistsException();
			} 
			// check if the filename contains fobiden characters
			boolean wellFormed = ResourceControl.isWellFormed(fileName);
			if ( !wellFormed ) {
				log.error("FtpAccessImpl::upload() "+fileName+" not well formed");
				throw new BadFormatException();
			}
			// we can send
			// goto directory
			String oldpwd = ftp.printWorkingDirectory();
			
			if ( ! ftp.changeWorkingDirectory(path) ) {
				log.error("FtpAccessImpl::upload() unable to change to directory :"+path);
				throw new NotExistsResourceException();
			} 
			boolean succes = ftp.storeFile(fileName, fileStream);
			if ( log.isDebugEnabled() ) 
				log.debug("FtpAccessImpl::upload() succes :"+succes);
			// close fileStream
			fileStream.close();

			//  restore old directory
			ftp.changeWorkingDirectory(oldpwd);

			// we check if the uploaded file is empty
			if ( log.isDebugEnabled() )
				log.debug("FtpAccessImpl::upload() checking file size");
//			if (fileIsEmptyOnServer(fileName, path)) {
//				// we delete the file
//				delete(fileName, path);
//				// we throw a new Exception
//				throw new EmptyFileOnUploadException();
//			}
			if ( log.isDebugEnabled() ) {
				log.debug("FtpAccessImpl::upload() : the end; returning");
			}
        	return true;
		} catch (IOException ex) {  
			log.error("FtpAccessImpl::upload() IOException ",ex);
			throw new ServerException();
		} 
//			catch (DeleteException ex) {
//    		log.error("FtpAccessImpl::upload() DeleteException",ex);
//			throw new UploadException();
//		}
	}
	
	
	/**
	 * Check if a file is empty on the server
	 * @param fileName the name of the file to check
	 * @param path the path where is the file on the server
	 * @return true if the file is empty on the server, false else
	 * @throws ApplicationException
	 */
	private boolean fileIsEmptyOnServer(String fileName, String path) throws ApplicationException {
		try {
			path = normalizePath(path);
			String pathAndName = path+fileName;
			
			// check if this is a valid resource
			if ( exists(pathAndName) ) {
				InputStream is = ftp.retrieveFileStream(pathAndName);
				int length = is.available();
				log.debug("FtpAccessImpl::fileIsEmptyOnServer() : length : "+length);
				/* close the input stream and finish the command 
				 * this is important of doing that because the stream
				 * must be ended to continue 
				 */
				is.close();
				if ( ! ftp.completePendingCommand() ) {
					log.error("FtpAccessImpl::fileIsEmptyOnServer() unable to completePendingCommand !");
				}
				return ( length == 0 )? true : false;
			} else {
				throw new NotExistsResourceException();
			}
		} catch (IOException e) {			
			log.error("FtpAccessImpl::fileIsEmptyOnServer() fileName="+fileName,e);
			throw new ApplicationException();
		} catch(ChannelException e) {
    		log.error("FtpAccessImpl::fileIsEmptyOnServer() fileName="+fileName,e);
			throw new ApplicationException();
		}
	}
	
	
	/**
	 * Delete the resource given in argument
	 * @param file file/directory to delete
	 * @param path path where is file/directory
	 * @return boolean true if well done else false
	 * @throws ApplicationException
	 * @throws DeleteException
	 * @throws NotAuthorizedDeleteException
	 * @throws NotExistsResourceException
	 * @throws NotAuthorizedException
	 */
	public boolean delete(String file, String path) throws ApplicationException, DeleteException, NotAuthorizedDeleteException, NotExistsResourceException, NotAuthorizedException {
		try {
			path = normalizePath(path);
			String cible = path+file;
			// check if directory
			boolean bIsDirectory = isDirectory(file,path);
			// create a new channel resource
			ChannelResource resource = new ChannelResource(file,file,cible,0,"text/plain",0,bIsDirectory, null);
			return delete(resource);
		} catch (ApplicationException ex) {
			log.error("FtpAccessImpl::delete() file="+file,ex);
			throw new ApplicationException();
		}
	}
	
	private boolean delete(ChannelResource resource) throws ApplicationException {
		try {
			if ( log.isDebugEnabled() ) 
				log.debug("FtpAccessImpl::delete() attempting to delete : "+resource.getPath()+" : "+resource.isCollection());
			if ( !resource.isCollection() ) {
				return ftp.deleteFile(resource.getPath());
			} else {
				ChannelResource[] resources = null;
				try {
					resources = ls(resource.getPath(), false);
				} catch (Exception e) {
					log.error("FtpAccessImpl::delete() resource="+resource.getPath(),e);
					// resource does not exists; end
					return false;
				}
				if ( resources != null ) {
					for (int i = 0; i < resources.length; i++) {
						if ( resources[i] != null )
							delete(resources[i]);
					}
				}
				return ftp.removeDirectory(resource.getPath());
			}
		} catch (IOException e) {
			log.error("FtpAccessImpl::delete() resource="+resource.getPath(),e);
			throw new ApplicationException();
		}
	}
	/**
	 * Rename the resource
	 * @param oldName file/directory to rename
	 * @param newName new name of the file/directory
	 * @param path path where is file/directory
	 * @return boolean true if well done else false
	 * @throws StillExistsException
	 * @throws BadFormatException
	 * @throws ApplicationException
	 * @throws RenameException
	 * @throws NotAuthorizedRenameException
	 * @throws NotExistsResourceException
	 * @throws NotAuthorizedException
	 */
	public boolean rename(String oldName, String newName, String path) throws StillExistsException, BadFormatException, ApplicationException, RenameException, NotAuthorizedRenameException, NotExistsResourceException, NotAuthorizedException, PropertiesException {
		try {
			//check if file exist on the Server
			path = normalizePath(path);
			String fullPath = path+newName;			
            boolean exist = exists(fullPath);
            if ( exist ) {
				log.error("FtpAccessImpl::rename() file="+newName+" already exist");
				throw new StillExistsException();            	
			}
			// check if the filename contains fobiden characters
			boolean wellFormed = ResourceControl.isWellFormed(newName);
			if ( !wellFormed ) { 
            	log.error("FtpAccessImpl::rename() file="+newName+" not well formed");
				throw new BadFormatException();
			}
			// we can rename
			String oldpwd = ftp.printWorkingDirectory();
			if ( ! ftp.changeWorkingDirectory(path) ) {
				log.error("FtpAccessImpl::rename() cannot change to directory : "+path);
				throw new NotExistsResourceException();
			}
			boolean result = ftp.rename(oldName,newName);
			ftp.changeWorkingDirectory(oldpwd);
			return result;
		} catch (IOException ex) {
			log.error("FtpAccessImpl::rename() file="+newName,ex);
			throw new RenameException();
		}        
	}
	
	/**
	 * Create the directory given in argument
	 * @param name file/directory to delete
	 * @param path path where is file/directory
	 * @return boolean true if well done else false
	 * @throws ApplicationException
	 * @throws CreateDirectoryException
	 * @throws StillExistsException
	 * @throws BadFormatException
	 * @throws NotAuthorizedNewDirException
	 * @throws NotExistsResourceException
	 * @throws NotAuthorizedException
	 */
	public boolean createDir (String name, String path) throws ApplicationException, CreateDirectoryException, StillExistsException, BadFormatException, NotAuthorizedNewDirException, NotExistsResourceException, NotAuthorizedException, PropertiesException{
		try {
			//look if a file with the same name exist
			path = normalizePath(path);
			String fullPath = path+name;			
			boolean exist = exists(fullPath);
			if ( exist ) {
        		log.error("FtpAccessImpl::createDir() name="+name+"  already exist");
				throw new StillExistsException();           	
			}
			// check if the file is well-formed
			boolean wellFormed = ResourceControl.isWellFormed(name);
			if ( !wellFormed ) {
            	log.error("FtpAccessImpl::createDir() name="+name+"  not well formed");
				throw new BadFormatException();
			}
			// we can create the directory
			String oldpwd = ftp.printWorkingDirectory();
			if ( ! ftp.changeWorkingDirectory(path) ) {
				log.error("FtpAccessImpl::createDir() path does not exists : "+path);
				throw new NotExistsResourceException();
			}
			wellFormed = ftp.makeDirectory(name);
			ftp.changeWorkingDirectory(oldpwd);
			return wellFormed;
		} catch (IOException ex) {
			log.error("FtpAccessImpl::createDir() name="+name,ex);
			throw new CreateDirectoryException();
		}
	}
	
	
	/**
	 * Check if we can paste here
	 * @param fromSpace the space from whitch we want to paste
	 * @param toSpaceKey the space key where we want to paste
	 * @param clipboardPath the path where is the clipboad
	 * @param clipboard the containt of the clipboard
	 * @param curentDirPath the path of the curent dir
	 * @return true if ok excpetion else
	 * @throws ApplicationException
	 * @throws PasteInChildDirectoryException
	 * @throws PasteWithSameNameException
	 * @throws PasteDeletedResourceException
	 * @throws NotAuthorizedException
	 */
	public boolean canPaste(Space fromSpace, String toSpaceKey, String clipboardPath, Vector clipboardNamesFrom, Vector clipboardNamesTo, String curentDirPath) throws ApplicationException, PasteInChildDirectoryException, PasteWithSameNameException, PasteDeletedResourceException, NotAuthorizedException {
		if ( log.isDebugEnabled() ) log.debug("FtpAccessImpl::canPaste called ...");
		// normalize
		curentDirPath = normalizePath(curentDirPath);
		clipboardPath = normalizePath(clipboardPath);
		// we check if there is a resource with the same name in the curent dir
		for (int i=0; i<clipboardNamesTo.size(); i++) {
			String resourceName = (String)clipboardNamesTo.elementAt(i);
			String fullPath = curentDirPath+resourceName;			
			boolean exist = exists(fullPath);
			if (exist)
				throw new PasteWithSameNameException();			
		}
		
		// we check if we try to paste a resource in its child
		if (toSpaceKey.equals(fromSpace.getKey())) {
			for (int i=0; i<clipboardNamesTo.size(); i++) {
				String resourceName = (String)clipboardNamesTo.elementAt(i);
				if (curentDirPath.startsWith(clipboardPath+resourceName+"/")) {				
					throw new PasteInChildDirectoryException();
				}					
			}
		}
		// we check if one of the selected resource has been deleted
		ServerAccess fromAccess = fromSpace.getServerAccessObject();
		//System.out.println("existe encore dans "+fromSpace.getLabel()+" ?");
		for (int i=0; i<clipboardNamesFrom.size(); i++) {
			String resourceName = (String)clipboardNamesFrom.elementAt(i);			
			String fullPath = clipboardPath+resourceName;
			boolean exist = fromAccess.exists(fullPath);
			if (!exist)
				throw new PasteDeletedResourceException();					
		}
		
		return true;
	}
	
	
	/************************
	 *      Copy methods
	 ************************/

	/**
	 * Local copy method
	 * @param resource resource to copy
	 * @param fromPath path where is the resource
	 * @param toPath path to copy the resource
	 * @return boolean true if well done else false
	 * @throws CopyException
	 * @throws ApplicationException
	 * @throws OverQuotaException
	 * @throws NotExistsResourceException
	 * @throws PasteNotAuthorizedResourceException
	 * @throws NotAuthorizedException
	 * @throws NotAuthorizedUploadException
	 */
	protected boolean localCopy(String resourceName, String fromPath, String toPath) throws CopyException, ApplicationException,OverQuotaException, NotExistsResourceException, PasteNotAuthorizedResourceException, NotAuthorizedException, NotAuthorizedUploadException {
		// localCopy is called when the space is the same
		if ( log.isDebugEnabled() )
			log.debug("FtpAccessImpl::localCopy() :"+resourceName+":"+fromPath+":"+toPath);
		try {
			// clone current ftp connection because we cannot read from
			// the current one and write into the same time
			FtpAccessImpl fromAccess = (FtpAccessImpl)this.clone();
			// connect
			fromAccess.connect();
			// cloned connection become the source access point
			// so we can call distantCopy
			boolean res = distantCopy(resourceName, resourceName, fromAccess, fromPath, toPath);
			// disconnect
			fromAccess.disconnect();
			return res;
		} catch(ServerException e) {
			log.error("FtpAccessImpl::localCopy() resourceName="+resourceName,e);
			throw new CopyException();
		} catch (BadConnexionParameters e) {
			log.error("FtpAccessImpl::localCopy() bad connexion parameter",e);
			throw new CopyException();
		}
	}
	
	
	/**
	 * The distant copy method
	 * @param resource resource to copy
	 * @param fromAccess the space access from witch we want to copy
	 * @param fromPath path where is the resource
	 * @param toPath path to copy the resource
	 * @return boolean true if well done else false
	 * @throws CopyException
	 * @throws ApplicationException
	 * @throws OverQuotaException
	 * @throws NotExistsResourceException
	 * @throws PasteNotAuthorizedResourceException
	 * @throws NotAuthorizedException
	 * @throws NotAuthorizedUploadException
	 * @throws ServerException
	 */
	protected boolean distantCopy(String resourceNameFrom, String resourceNameTo, ServerAccess fromAccess, String fromPath, String toPath) throws CopyException, ApplicationException,OverQuotaException, NotExistsResourceException, PasteNotAuthorizedResourceException, NotAuthorizedException, NotAuthorizedUploadException, ServerException {
		if ( log.isDebugEnabled() ) 
			log.error("FtpAccessImpl::distantCopy (1) called : "+resourceNameFrom+","+resourceNameTo+","+fromPath+","+toPath);
		// normalize
		fromPath = normalizePath(fromPath);
		toPath = normalizePath(toPath);

		// check if resource is a directory   
		boolean bIsDirectory = fromAccess.isDirectory(resourceNameFrom, fromPath);	
		
		// if file
		if (!bIsDirectory) {
			String absoluteFromPath = fromPath+resourceNameFrom;
			distantCopyFile(resourceNameTo, fromAccess, absoluteFromPath, toPath);
			return true;
		}
		
		// if directory
		else {
			String absoluteFromPath = fromPath+resourceNameFrom;
			String name = resourceNameTo;
			if (!name.endsWith("/")) {
				name = name+"/";
			}
			if (!absoluteFromPath.endsWith("/")) {
				absoluteFromPath = absoluteFromPath+"/";
			}
			distantCopyDirectory(name, fromAccess, absoluteFromPath, toPath);
			return true;
		}
	}
	
	/**
	 * The distant copy method
	 * @param resource resource to copy
	 * @param fromAccess the space access from witch we want to copy 
	 * @param toPath path to copy the resource
	 * @return boolean true if well done else false
	 * @throws CopyException
	 * @throws ApplicationException
	 * @throws OverQuotaException
	 * @throws PasteNotAuthorizedResourceException
	 * @throws NotExistsResourceException
	 * @throws PasteNotAuthorizedResourceException
	 * @throws NotAuthorizedException
	 * @throws NotAuthorizedUploadException
	 * @throws ServerException
	 */
	private void distantCopy(ChannelResource resource, ServerAccess fromAccess, String toPath) throws CopyException, ApplicationException,OverQuotaException, PasteNotAuthorizedResourceException, NotExistsResourceException, PasteNotAuthorizedResourceException, NotAuthorizedException, NotAuthorizedUploadException, ServerException {
		if ( log.isDebugEnabled() ) log.debug("FtpAccessImpl::distantCopy (2) called ....");
		// check if resource is a directory   
		boolean bIsDirectory = resource.isCollection(); 	
		// resource name
		String name = resource.getName();
		// resource full path
		String absoluteFromPath = resource.getPath();
		
		// if file
		if (!bIsDirectory) {
			distantCopyFile(name, fromAccess, absoluteFromPath, toPath);
		}
		// if directory
		else {
			if (!name.endsWith("/")) {
				name = name+"/";
			}
			if (!absoluteFromPath.endsWith("/")) {
				absoluteFromPath = absoluteFromPath+"/";
			}
			distantCopyDirectory(name, fromAccess, absoluteFromPath, toPath);
		}	
	}
	
	/**
	 * The distant copy method of a file
	 * @param fileName file to copy
	 * @param fromAccess the space access from witch we want to copy
	 * @param absoluteFromPath path where is the resource
	 * @param toPath path to copy the resource
	 * @return boolean true if well done else false
	 * @throws CopyException
	 * @throws ApplicationException
	 * @throws OverQuotaException
	 * @throws PasteNotAuthorizedResourceException
	 * @throws NotExistsResourceException
	 * @throws PasteNotAuthorizedResourceException
	 * @throws NotAuthorizedException
	 * @throws NotAuthorizedUploadException
	 */
	private void distantCopyFile(String resourceNameTo, ServerAccess fromAccess, String absoluteFromPath, String toPath) throws CopyException, ApplicationException,OverQuotaException, PasteNotAuthorizedResourceException, NotExistsResourceException, PasteNotAuthorizedResourceException, NotAuthorizedException, NotAuthorizedUploadException {
		if ( log.isDebugEnabled() ) log.debug("FtpAccessImpl::distantCopyFile() called ...");
		try {
			// absolute path to copy the resource
			toPath = normalizePath(toPath);
			String absoluteToPath = toPath+resourceNameTo;							
			if ( log.isDebugEnabled() )	
				log.debug("FtpAccessImpl::distantCopyFile() copying from "+absoluteFromPath+" to "+absoluteToPath);

			// get the resource from the server			
			InputStream streamFromFile = fromAccess.getMethodData(absoluteFromPath);
			// if not exists
			if (streamFromFile==null) {
				throw new NotExistsResourceException();
			}
			// if the resource exists
			// put the stream to the new server
			boolean res = ftp.storeFile(absoluteToPath, streamFromFile);
			streamFromFile.close();	
			if ( !res ) {
				throw new CopyException();
			}
		} catch (IOException ex) {
			log.error("FtpAccessImpl::distantCopyFile() resourceNameTo="+resourceNameTo,ex);
			throw new CopyException();
		}
	}
	
	
	/**
	 * The distant copy method of a directory
	 * @param directoryName file to directory
	 * @param fromAccess the space access from witch we want to copy
	 * @param absoluteFromPath path where is the resource
	 * @param toPath path to copy the resource
	 * @return boolean true if well done else false
	 * @throws CopyException
	 * @throws ApplicationException
	 * @throws OverQuotaException
	 * @throws PasteNotAuthorizedResourceException
	 * @throws NotExistsResourceException
	 * @throws PasteNotAuthorizedResourceException
	 * @throws NotAuthorizedException
	 * @throws NotAuthorizedUploadException
	 * @throws ServerException
	 */
	private void distantCopyDirectory(String resourceNameTo, ServerAccess fromAccess, String absoluteFromPath, String toPath) throws CopyException, ApplicationException,OverQuotaException, PasteNotAuthorizedResourceException, NotExistsResourceException, PasteNotAuthorizedResourceException, NotAuthorizedException, NotAuthorizedUploadException, ServerException {
		if ( log.isDebugEnabled() ) log.debug("FtpAccessImpl::distantCopyDirectory() called ...");
		try {
		
			// absolute path to copy the resource
			toPath = normalizePath(toPath);
			String absoluteToPath = toPath+resourceNameTo;							
			// if exists from path
			boolean exists = fromAccess.exists(absoluteFromPath);
			if (exists) {				
				// create this directory onto the new server
				this.ftp.makeDirectory(absoluteToPath);
				// list the resources into this directory				
				ChannelResource[] resources = fromAccess.ls(absoluteFromPath, false);
				// for each resource, we copy
				for (int i=0; i<resources.length; i++) {
					distantCopy(resources[i], fromAccess, absoluteToPath);
				}				
			} else {											
				throw new NotExistsResourceException();
			}    
		} catch (IOException ex) {
			log.error("FtpAccessImpl::distantCopyDirectory() resourceNameTo="+resourceNameTo,ex);
			throw new CopyException();
		}
	}
	
	
	/************************
	 *      Move methods
	 ************************/
	
	
	/**
	 * Local move method
	 * @param resource resource to move
	 * @param fromPath path where is the resource
	 * @param toPath path to copy the resource
	 * @return boolean true if well done else false
	 * @throws MoveException
	 * @throws ApplicationException
	 * @throws OverQuotaException
	 * @throws PasteNotAuthorizedResourceException
	 * @throws NotExistsResourceException
	 * @throws PasteNotAuthorizedResourceException
	 * @throws NotAuthorizedException
	 * @throws NotAuthorizedUploadException
	 */
	protected boolean localMove(String resource, String fromPath, String toPath) throws MoveException, ApplicationException,OverQuotaException, PasteNotAuthorizedResourceException, NotExistsResourceException, PasteNotAuthorizedResourceException, NotAuthorizedException, NotAuthorizedUploadException {
		// localy move fromPath+resource to toPath+resource
		// in FTP localMove consist to rename the entire source path to the new
		// destination path
		if ( log.isDebugEnabled() ) 
			log.debug("FtpAccessImpl::localMove() called with : "+resource+","+fromPath+","+toPath);
		try {
			toPath = normalizePath(toPath);
			String fullToPath = toPath+resource;
			fromPath = normalizePath(fromPath);
			String fullFromPath = fromPath+resource;

			// check if path are the same
			if ( fullFromPath.equals(fullToPath) ) {
				log.error("FtpAccessImpl::localMove() source an destination are the same : "+fullFromPath);
				throw new MoveException();
			}
			// check if the destination file exists on the Server
			if ( exists(fullToPath) ) {
				log.error("FtpAccessImpl::localMove() resource="+fullToPath+" already exists");
				throw new MoveException();
			}
			// check if source exists
			if ( ! exists(fullFromPath) ) {
				log.error("FtpAccessImpl::localMove() resourc="+fullFromPath+" does not exists");
				throw new NotExistsResourceException();
			}
			// we can rename 
			return ftp.rename(fullFromPath,fullToPath);
		} catch (IOException e) {
			log.error("FtpAccessImpl::localMove() resource="+resource,e);
			throw new MoveException();
		}
	}
	
	/**
	 * Return the resource associated to the file to download
	 * @param name the file name
	 * @param path the path where to find the file
	 * @return the ChannelWebdavResource
	 * @throws DownloadException
	 */
	public ChannelResource download(String name,String path) throws DownloadException {
		try {
			if ( ! ftp.isConnected() ) {
				connect();
			}
		} catch(Exception e) {
			log.error("FtpAccessImpl::download() name="+name,e);
			throw new DownloadException();
		}
		try {
			path = normalizePath(path);
			InputStream is = ftp.retrieveFileStream(path+name);
			int length = is.available();
			FtpInputStream fis = null;
			if ( is != null ) 
				fis = new FtpInputStream(ftp,is);
			String contentType =  getContentTypeFromName(name);
			return new ChannelResource(name, name, path+name, length, contentType, 0, false, fis, null);
		} catch(IOException ex){			
			log.error("FtpAccessImpl::download() name="+name,ex);
			throw new DownloadException();
		}
	}
	
	
	/************************
	 * ftp control methods
	 ************************/
	
	/**
	 * Check is the resource exists
	 * @param resourceName name of the file/directory to verify
	 * @param path path to find the file/directory
	 * @return true if file/directory exists else false
	 * @throws ApplicationException
	 * @throws NotAuthorizedException
	 */
	public boolean exists(String resourceName, String path) throws ApplicationException, NotAuthorizedException {
			path = normalizePath(path);
			String pathAndName = path+resourceName;			
			return exists(pathAndName);
	}
	
	/**
	 * Check is the resource exists
	 * @param fullPath path to find the file/directory
	 * @return true if file/directory exists else false
	 * @throws ApplicationException
	 * @throws NotAuthorizedException
	 */
	public boolean exists(String fullPath) throws ApplicationException, NotAuthorizedException {
		return exist(fullPath);
	}
	
	
	/**
	 * Check is the resource exists
	 * @param fullPath path to find the file/directory
	 * @return true if file/directory exists else false
	 * @throws ApplicationException
	 * @throws NotAuthorizedException
	 */
	private boolean exist(String fullPath) throws ApplicationException, NotAuthorizedException {
		if ( log.isDebugEnabled() ) log.debug("FtpAccessImpl::exist() "+fullPath);
		try {
			// is a directory 
			if ( ! isDirectory(fullPath) ) {
				InputStream input = ftp.retrieveFileStream(fullPath);
				if ( input != null ) {
					// ok there is an InputStream; this is a file
					input.close();
					ftp.completePendingCommand();
					return true;
				} else {
					return false;
				}
			} else {
				// ok resource exists and is a directory
				return true;
			}
//		} catch (BadConnexionParameters e) {
//			log.error("FtpAccessImpl::exists() doesn't exist fullPath="+fullPath,e);
//			return false;
//		} catch (ServerException e) {
//			log.error("FtpAccessImpl::exists() doesn't exist fullPath="+fullPath,e);
//			return false;
		} catch (IOException e) {
			log.error("FtpAccessImpl::exists() doesn't exist fullPath="+fullPath,e);
			return false;
		} catch (ApplicationException e) {
			log.error("FtpAccessImpl::exists() doesn't exist fullPath="+fullPath,e);
			return false;
		}
	}
	
	/**
	 * Check if we are authorized to read this resource
	 * @param resourceName name of the file/directory to verify
	 * @param path path to find the file/directory
	 * @return true if authorized else false
	 * @throws ApplicationException
	 */
	public boolean canRead(String resourceName, String path) throws ApplicationException {
		path = normalizePath(path);
		return canRead(path+resourceName);
	}
	
	
	/**
	 * Check if we are authorized to read this resource
	 * @param path path to find the file/directory
	 * @return true if authorized else false
	 * @throws ApplicationException
	 */
	public boolean canRead(String path) throws ApplicationException {
		if ( log.isDebugEnabled() ) log.debug("FtpAccessImpl::canRead() "+path);
		try {
			return exists(path);
		} catch (NotAuthorizedException e) {	
			log.error("FtpAccessImpl::canRead() path="+path,e);
			return false;
		}
	}
	
	/**
	 * Check if the resource named name is a directory in the path given
	 * @param name the name of the resource
	 * @param path the path
	 * @return true if directory, false else
	 * @throws ApplicationException
	 * @throws NotAuthorizedException
	 * @throws NotExistsResourceException
	 */
	public boolean isDirectory(String name, String path) throws ApplicationException, NotAuthorizedException, NotExistsResourceException {
		path = normalizePath(path);
		String fullPath = path+name;
		try {
			return isDirectory(fullPath);
		} catch ( ApplicationException e ) {
			throw e;
		}
	}
	
	/**
	 * check if the given path is a directory
	 * @param fullPath
	 * @return
	 * @throws ApplicationException
	 */
	private boolean isDirectory(String fullPath) throws ApplicationException {
		try {
			if ( ! ftp.isConnected() ) {
				connect();
			}
		} catch (ServerException e) {
			log.error("FtpAccessImpl::isDirectory() fullPath="+fullPath,e);
			throw new ApplicationException();
		} catch (BadConnexionParameters e) {
			log.error("FtpAccessImpl::isDirectory() fullPath="+fullPath,e);
			throw new ApplicationException();
		}
		try {
			String oldpwd = ftp.printWorkingDirectory();
			//boolean res = ftp.changeWorkingDirectory(path+name);
			boolean res = ftp.changeWorkingDirectory(fullPath);
			ftp.changeWorkingDirectory(oldpwd);
			return res;
		} catch (IOException e) {
			log.error("FtpAccessImpl::isDirectory() IOException : directory fullPath="+fullPath,e);
			throw new ApplicationException();
		}	
	}

	/**
	 * Check if the directory named dir is empty or not. We suppose that dir has been checked as a directory before
	 * @param dir the name of the directory
	 * @param path the path
	 * @return true if empty, false else
	 * @throws ApplicationException
	 * @throws NotExistsResourceException
	 * @throws NotAuthorizedException
	 */
	public boolean isEmpty (String dir, String path) throws ApplicationException, NotExistsResourceException, NotAuthorizedException {		
		if ( log.isDebugEnabled() ) 
			log.debug("FtpAccessImpl::isEmpty() dir="+dir+" , path="+path);
		try {			
			path = normalizePath(path);
			// we construct the path
			String pathToList = path+dir;
			// we count the number of resources
			return ( ls(pathToList, false).length > 0 ) ? false : true;
		} catch (ApplicationException e) {
			log.error("FtpAccessImpl::isEmpty() dir="+dir,e);
			throw e;
		} catch (NotExistsResourceException e) {
			log.error("FtpAccessImpl::isEmpty() dir="+dir+" does not exists", e);
			throw e;
		}
	}	
	
	/**
	 * Get the quota for a specified path
	 * @param path the path of the resource
	 * @return an array of long. 1rst is the available bytes. 2nd is the total bytes
	 */
	public long[] getQuota(String path) {
		// implement Quota here
		return null;
	}
	
	
	/**
	 * not implemented
	 */
	public String getProperty(String namespace, String path, String propertyName) throws ServerException {
		return null;
	}// getProperty

	/**
	 * not implemented
	 */
	public boolean setProperty(String namespace, String path, String propertyName, String propertyValue) throws ServerException {
		return false;
	}// setProperty

	/**
	 * not implemented
	 */
	/*public EsupPermissions getPermissions(String path, String permissionType) throws AclReadException, AclAccessException, NotSupportedAclException {
		throw new NotSupportedAclException();
	}*/// getPermissions

	/**
	 * not implemented
	 */
	public EsupPermissions getPermissions(String path) throws AclReadException, AclAccessException, NotSupportedAclException {
		throw new NotSupportedAclException();
	}// getPermissions

	/**
	 * not implemented
	 */
	public void grant(String path, String principal, String permissionType, boolean fixNow) throws AclReadException, AclAccessException, AclWriteException, NotSupportedAclException {
	}// grant

	/**
	 * not implemented
	 */
	public void deny(String path, String principal, String permissionType, boolean fixNow) throws AclReadException, AclAccessException, AclWriteException, NotSupportedAclException {
	}// deny

	/**
	 * not implemented
	 */
	public void revoke(String path, String principal, String permissionType, boolean fixNow) throws AclReadException, AclAccessException, AclWriteException, NotSupportedAclException {
	}// revoke

	/**
	 * not implemented
	 */
	public void revokeAllPermissons(String path, boolean fixNow) throws AclReadException, AclAccessException, AclWriteException, NotSupportedAclException {
	}
	
	/**
	 * not implemented
	 */
	public void fixPermissions(String path) throws AclReadException, AclAccessException, AclWriteException, NotSupportedAclException {
	}
	
	/**
	 * not implemented
	 */
	public void initTemporaryWorkingPermissions(String path) throws NotSupportedAclException {
	}
	
	/**
	 * not implemented
	 */
	/*public Vector hasInheritedPermission(String path, String principal, String permissionType, boolean negative) throws AclReadException, AclAccessException, NotSupportedAclException {
		return null;
	}*/// hasInheritedPermission

	/**
	 * not implemented
	 */
	/*public boolean hasNotInheritedPermission(String path, String principal, String permissionType, boolean negative) throws AclReadException, AclAccessException, NotSupportedAclException {
		return false;
	}*/// hasNotInheritedPermission


	/**
	 * Check The privileges for the current user
	 * @param path the path of the space
	 * @return an array containing the values of reading, writing and sharing
	 */
	public boolean[] checkPrivileges(String path) {
		boolean[] results = new boolean[3];
		results[0] = true;
		results[1] = true;
		results[2] = false;
		return results;
	}

	/**
	 * simply append "/" to path if needed
	 * @param path the path to normalize
	 * @return the normalized path
	 */
	protected String normalizePath(String path) {
		if ( !path.endsWith("/") )
			path += "/";
		return path;
	}
	/**
	 * clone this object
	 */
	public Object clone() {
		FtpAccessImpl clone = new FtpAccessImpl();
		
		// ftp client conf
		clone.ftpControlEncoding = this.ftpControlEncoding;
		clone.ftpUsePasvMode = this.ftpUsePasvMode;
		clone.ftpType = this.ftpType;
		clone.ftpBufferSize = this.ftpBufferSize;
		clone.ftpFileType = this.ftpFileType;
		// end ftp client conf
		clone.login = this.login;
		clone.password = this.password;
		clone.useAuthCache = this.useAuthCache;
		clone.host = this.host;
		clone.space = this.space;
		clone.ftp = initFTPClient();
		return clone;
	}
}





/**
 * FtpInputStream est un wrapper de la classe Apache : {@link org.apache.commons.net.SocketClient}.
 * Le but est de fermer correctement le flux du transfert ftp.
 * En effet, lorsque l'on récupère un flux de transfert ftp via la méthode 
 * {@link org.apache.commons.net.ftp.FTPClient#retrieveFileStream retrieveFileStream} de la classe
 * {@link org.apache.commons.net.ftp.FTPClient}; ce flux doit être correctement terminer avec la
 * méthode {@link org.apache.commons.net.ftp.FTPClient#completePendingCommand completePendingCommand}.
 * Si cette méthode n'est pas invoquée, il n'est plus possible d'envoyé de commandes au serveur
 * FTP, ce qui provoque un timeout dans le canal de stockage.
 * exemple d'usage :
 * <pre>
 * 	package org.esupportail.portal.channels.CStockage.provider.access;
 * 	....
 *	public class FtpAccessImpl extends ServerAccess {
 *		....
 * 		public ChannelResource download(String name, String path) throws DownloadException {
 * 			try {
 * 				InputStream is = ftp.retrieveFileStream(path+name);
 * 				FtpInputStream fis = new FtpInputStream(ftp,is);
 * 				int length = is.available();
 * 				return new ChannelResource(name, path+name, length, "text/plain", 0, false, fis);
 * 			} catch (IOException e) {
 * 				...
 * 				throw new DownloadException();
 * 			}
 * 		}
 * 	}
 * </pre>
 */
class FtpInputStream extends InputStream {

	protected static final Log log = LogFactory.getLog(FtpInputStream.class);
	private FTPClient ftp = null;
	private InputStream input = null;
		
	/**
	 * Constructor
	 * @param ftp the FTPClient object
	 * @param input the InputStream of the FTPClient.retrieveFileStream method
	 */
	public FtpInputStream(FTPClient ftp, InputStream input) {
		this.ftp = ftp;
		this.input = input;
		if ( log.isDebugEnabled() )
			log.debug("FtpInputStream() : input : "+input.getClass().getName());
	}

	// lets override
	public int read() throws IOException {
		try {
			return input.read();
		} catch (IOException e) {
			throw e;
		}
	}
	public int read(byte[] b) throws IOException {
		try {
			return input.read(b);
		} catch (IOException e) {
			throw e;
		}
	}
	public int read(byte[] b,int off,int len) throws IOException {
		try {
			return input.read(b,off,len);
		} catch (IOException e) {
			throw e;
		}
	}
	public long skip(long n) throws IOException {
		try {
			return input.skip(n);
		} catch (IOException e) {
			throw e;
		}
	}
	public int available() throws IOException {
		try {
			return input.available();
		} catch (IOException e) {
			throw e;
		}
	}
	public void close() throws IOException {
		try {
			input.close();
			if ( log.isDebugEnabled() ) {
				log.debug("FtpInputStream::close() attempting to complete Pending commands");
			}
			ftp.completePendingCommand();
		} catch (IOException e) {
			throw e;
		}
	}
	public void mark(int readlimit) {
		input.mark(readlimit);
	}
	public void reset() throws IOException {
		try {
			input.reset();
		} catch (IOException e) {
			throw e;
		}
	}
	public boolean markSupported() {
		return input.markSupported();
	}
}

