Save This Page
Home » apache-ant-1.8.1 » org.apache.tools » ant » taskdefs » optional » net » [javadoc | source]
    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one or more
    3    *  contributor license agreements.  See the NOTICE file distributed with
    4    *  this work for additional information regarding copyright ownership.
    5    *  The ASF licenses this file to You under the Apache License, Version 2.0
    6    *  (the "License"); you may not use this file except in compliance with
    7    *  the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    *  Unless required by applicable law or agreed to in writing, software
   12    *  distributed under the License is distributed on an "AS IS" BASIS,
   13    *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    *  See the License for the specific language governing permissions and
   15    *  limitations under the License.
   16    *
   17    */
   18   package org.apache.tools.ant.taskdefs.optional.net;
   19   
   20   import java.io.BufferedInputStream;
   21   import java.io.BufferedOutputStream;
   22   import java.io.BufferedWriter;
   23   import java.io.File;
   24   import java.io.FileInputStream;
   25   import java.io.FileOutputStream;
   26   import java.io.FileWriter;
   27   import java.io.IOException;
   28   import java.io.InputStream;
   29   import java.io.OutputStream;
   30   import java.text.SimpleDateFormat;
   31   import java.util.Collection;
   32   import java.util.Date;
   33   import java.util.Enumeration;
   34   import java.util.HashMap;
   35   import java.util.HashSet;
   36   import java.util.Hashtable;
   37   import java.util.Iterator;
   38   import java.util.Locale;
   39   import java.util.Map;
   40   import java.util.Set;
   41   import java.util.StringTokenizer;
   42   import java.util.Vector;
   43   
   44   import org.apache.commons.net.ftp.FTPClient;
   45   import org.apache.commons.net.ftp.FTPClientConfig;
   46   import org.apache.commons.net.ftp.FTPFile;
   47   import org.apache.commons.net.ftp.FTPReply;
   48   import org.apache.tools.ant.BuildException;
   49   import org.apache.tools.ant.DirectoryScanner;
   50   import org.apache.tools.ant.Project;
   51   import org.apache.tools.ant.Task;
   52   import org.apache.tools.ant.taskdefs.Delete;
   53   import org.apache.tools.ant.types.EnumeratedAttribute;
   54   import org.apache.tools.ant.types.FileSet;
   55   import org.apache.tools.ant.types.selectors.SelectorUtils;
   56   import org.apache.tools.ant.util.FileUtils;
   57   import org.apache.tools.ant.util.RetryHandler;
   58   import org.apache.tools.ant.util.Retryable;
   59   import org.apache.tools.ant.util.VectorSet;
   60   
   61   /**
   62    * Basic FTP client. Performs the following actions:
   63    * <ul>
   64    *   <li> <strong>send</strong> - send files to a remote server. This is the
   65    *   default action.</li>
   66    *   <li> <strong>get</strong> - retrieve files from a remote server.</li>
   67    *   <li> <strong>del</strong> - delete files from a remote server.</li>
   68    *   <li> <strong>list</strong> - create a file listing.</li>
   69    *   <li> <strong>chmod</strong> - change unix file permissions.</li>
   70    *   <li> <strong>rmdir</strong> - remove directories, if empty, from a
   71    *   remote server.</li>
   72    * </ul>
   73    * <strong>Note:</strong> Some FTP servers - notably the Solaris server - seem
   74    * to hold data ports open after a "retr" operation, allowing them to timeout
   75    * instead of shutting them down cleanly. This happens in active or passive
   76    * mode, and the ports will remain open even after ending the FTP session. FTP
   77    * "send" operations seem to close ports immediately. This behavior may cause
   78    * problems on some systems when downloading large sets of files.
   79    *
   80    * @since Ant 1.3
   81    */
   82   public class FTP extends Task implements FTPTaskConfig {
   83       protected static final int SEND_FILES = 0;
   84       protected static final int GET_FILES = 1;
   85       protected static final int DEL_FILES = 2;
   86       protected static final int LIST_FILES = 3;
   87       protected static final int MK_DIR = 4;
   88       protected static final int CHMOD = 5;
   89       protected static final int RM_DIR = 6;
   90       protected static final int SITE_CMD = 7;
   91       /** return code of ftp - not implemented in commons-net version 1.0 */
   92       private static final int CODE_521 = 521;
   93   
   94       /** adjust uptodate calculations where server timestamps are HH:mm and client's
   95        * are HH:mm:ss */
   96       private static final long GRANULARITY_MINUTE = 60000L;
   97   
   98       /** Date formatter used in logging, note not thread safe! */
   99       private static final SimpleDateFormat TIMESTAMP_LOGGING_SDF =
  100           new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  101   
  102       /** Default port for FTP */
  103       public static final int DEFAULT_FTP_PORT = 21;
  104   
  105       private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
  106   
  107       private String remotedir;
  108       private String server;
  109       private String userid;
  110       private String password;
  111       private String account;
  112       private File listing;
  113       private boolean binary = true;
  114       private boolean passive = false;
  115       private boolean verbose = false;
  116       private boolean newerOnly = false;
  117       private long timeDiffMillis = 0;
  118       private long granularityMillis = 0L;
  119       private boolean timeDiffAuto = false;
  120       private int action = SEND_FILES;
  121       private Vector filesets = new Vector();
  122       private Set dirCache = new HashSet();
  123       private int transferred = 0;
  124       private String remoteFileSep = "/";
  125       private int port = DEFAULT_FTP_PORT;
  126       private boolean skipFailedTransfers = false;
  127       private int skipped = 0;
  128       private boolean ignoreNoncriticalErrors = false;
  129       private boolean preserveLastModified = false;
  130       private String chmod = null;
  131       private String umask = null;
  132       private FTPSystemType systemTypeKey = FTPSystemType.getDefault();
  133       private String defaultDateFormatConfig = null;
  134       private String recentDateFormatConfig = null;
  135       private LanguageCode serverLanguageCodeConfig = LanguageCode.getDefault();
  136       private String serverTimeZoneConfig = null;
  137       private String shortMonthNamesConfig = null;
  138       private Granularity timestampGranularity = Granularity.getDefault();
  139       private boolean isConfigurationSet = false;
  140       private int retriesAllowed = 0;
  141       private String siteCommand = null;
  142       private String initialSiteCommand = null;
  143       private boolean enableRemoteVerification = true;
  144   
  145       protected static final String[] ACTION_STRS = {
  146           "sending",
  147           "getting",
  148           "deleting",
  149           "listing",
  150           "making directory",
  151           "chmod",
  152           "removing",
  153           "site"
  154       };
  155   
  156       protected static final String[] COMPLETED_ACTION_STRS = {
  157           "sent",
  158           "retrieved",
  159           "deleted",
  160           "listed",
  161           "created directory",
  162           "mode changed",
  163           "removed",
  164           "site command executed"
  165       };
  166   
  167       protected static final String[] ACTION_TARGET_STRS = {
  168           "files",
  169           "files",
  170           "files",
  171           "files",
  172           "directory",
  173           "files",
  174           "directories",
  175           "site command"
  176       };
  177   
  178       /**
  179        * internal class providing a File-like interface to some of the information
  180        * available from the FTP server
  181        *
  182        */
  183       protected static class FTPFileProxy extends File {
  184   
  185           private final FTPFile file;
  186           private final String[] parts;
  187           private final String name;
  188   
  189           /**
  190            * creates a proxy to a FTP file
  191            * @param file
  192            */
  193           public FTPFileProxy(FTPFile file) {
  194               super(file.getName());
  195               name = file.getName();
  196               this.file = file;
  197               parts = FileUtils.getPathStack(name);
  198           }
  199   
  200           /**
  201            * creates a proxy to a FTP directory
  202            * @param completePath the remote directory.
  203            */
  204           public FTPFileProxy(String completePath) {
  205               super(completePath);
  206               file = null;
  207               name = completePath;
  208               parts = FileUtils.getPathStack(completePath);
  209           }
  210   
  211   
  212           /* (non-Javadoc)
  213            * @see java.io.File#exists()
  214            */
  215           public boolean exists() {
  216               return true;
  217           }
  218   
  219   
  220           /* (non-Javadoc)
  221            * @see java.io.File#getAbsolutePath()
  222            */
  223           public String getAbsolutePath() {
  224               return name;
  225           }
  226   
  227   
  228           /* (non-Javadoc)
  229            * @see java.io.File#getName()
  230            */
  231           public String getName() {
  232               return parts.length > 0 ? parts[parts.length - 1] : name;
  233           }
  234   
  235   
  236           /* (non-Javadoc)
  237            * @see java.io.File#getParent()
  238            */
  239           public String getParent() {
  240               String result = "";
  241               for(int i = 0; i < parts.length - 1; i++){
  242                   result += File.separatorChar + parts[i];
  243               }
  244               return result;
  245           }
  246   
  247   
  248           /* (non-Javadoc)
  249            * @see java.io.File#getPath()
  250            */
  251           public String getPath() {
  252               return name;
  253           }
  254   
  255   
  256           /**
  257            * FTP files are stored as absolute paths
  258            * @return true
  259            */
  260           public boolean isAbsolute() {
  261               return true;
  262           }
  263   
  264   
  265           /* (non-Javadoc)
  266            * @see java.io.File#isDirectory()
  267            */
  268           public boolean isDirectory() {
  269               return file == null;
  270           }
  271   
  272   
  273           /* (non-Javadoc)
  274            * @see java.io.File#isFile()
  275            */
  276           public boolean isFile() {
  277               return file != null;
  278           }
  279   
  280   
  281           /**
  282            * FTP files cannot be hidden
  283            *
  284            * @return  false
  285            */
  286           public boolean isHidden() {
  287               return false;
  288           }
  289   
  290   
  291           /* (non-Javadoc)
  292            * @see java.io.File#lastModified()
  293            */
  294           public long lastModified() {
  295               if (file != null) {
  296                   return file.getTimestamp().getTimeInMillis();
  297               }
  298               return 0;
  299           }
  300   
  301   
  302           /* (non-Javadoc)
  303            * @see java.io.File#length()
  304            */
  305           public long length() {
  306               if (file != null) {
  307                   return file.getSize();
  308               }
  309               return 0;
  310           }
  311       }
  312   
  313       /**
  314        * internal class allowing to read the contents of a remote file system
  315        * using the FTP protocol
  316        * used in particular for ftp get operations
  317        * differences with DirectoryScanner
  318        * "" (the root of the fileset) is never included in the included directories
  319        * followSymlinks defaults to false
  320        */
  321       protected class FTPDirectoryScanner extends DirectoryScanner {
  322           // CheckStyle:VisibilityModifier OFF - bc
  323           protected FTPClient ftp = null;
  324           // CheckStyle:VisibilityModifier ON
  325   
  326           private String rootPath = null;
  327   
  328           /**
  329            * since ant 1.6
  330            * this flag should be set to true on UNIX and can save scanning time
  331            */
  332           private boolean remoteSystemCaseSensitive = false;
  333           private boolean remoteSensitivityChecked = false;
  334   
  335           /**
  336            * constructor
  337            * @param ftp  ftpclient object
  338            */
  339           public FTPDirectoryScanner(FTPClient ftp) {
  340               super();
  341               this.ftp = ftp;
  342               this.setFollowSymlinks(false);
  343           }
  344   
  345   
  346           /**
  347            * scans the remote directory,
  348            * storing internally the included files, directories, ...
  349            */
  350           public void scan() {
  351               if (includes == null) {
  352                   // No includes supplied, so set it to 'matches all'
  353                   includes = new String[1];
  354                   includes[0] = "**";
  355               }
  356               if (excludes == null) {
  357                   excludes = new String[0];
  358               }
  359   
  360               filesIncluded = new VectorSet();
  361               filesNotIncluded = new Vector();
  362               filesExcluded = new VectorSet();
  363               dirsIncluded = new VectorSet();
  364               dirsNotIncluded = new Vector();
  365               dirsExcluded = new VectorSet();
  366   
  367               try {
  368                   String cwd = ftp.printWorkingDirectory();
  369                   // always start from the current ftp working dir
  370                   forceRemoteSensitivityCheck();
  371   
  372                   checkIncludePatterns();
  373                   clearCaches();
  374                   ftp.changeWorkingDirectory(cwd);
  375               } catch (IOException e) {
  376                   throw new BuildException("Unable to scan FTP server: ", e);
  377               }
  378           }
  379   
  380   
  381           /**
  382            * this routine is actually checking all the include patterns in
  383            * order to avoid scanning everything under base dir
  384            * @since ant1.6
  385            */
  386           private void checkIncludePatterns() {
  387   
  388               Hashtable newroots = new Hashtable();
  389               // put in the newroots vector the include patterns without
  390               // wildcard tokens
  391               for (int icounter = 0; icounter < includes.length; icounter++) {
  392                   String newpattern =
  393                       SelectorUtils.rtrimWildcardTokens(includes[icounter]);
  394                   newroots.put(newpattern, includes[icounter]);
  395               }
  396               if (remotedir == null) {
  397                   try {
  398                       remotedir = ftp.printWorkingDirectory();
  399                   } catch (IOException e) {
  400                       throw new BuildException("could not read current ftp directory",
  401                                                getLocation());
  402                   }
  403               }
  404               AntFTPFile baseFTPFile = new AntFTPRootFile(ftp, remotedir);
  405               rootPath = baseFTPFile.getAbsolutePath();
  406               // construct it
  407               if (newroots.containsKey("")) {
  408                   // we are going to scan everything anyway
  409                   scandir(rootPath, "", true);
  410               } else {
  411                   // only scan directories that can include matched files or
  412                   // directories
  413                   Enumeration enum2 = newroots.keys();
  414   
  415                   while (enum2.hasMoreElements()) {
  416                       String currentelement = (String) enum2.nextElement();
  417                       String originalpattern = (String) newroots.get(currentelement);
  418                       AntFTPFile myfile = new AntFTPFile(baseFTPFile, currentelement);
  419                       boolean isOK = true;
  420                       boolean traversesSymlinks = false;
  421                       String path = null;
  422   
  423                       if (myfile.exists()) {
  424                           forceRemoteSensitivityCheck();
  425                           if (remoteSensitivityChecked
  426                               && remoteSystemCaseSensitive && isFollowSymlinks()) {
  427                               // cool case,
  428                               //we do not need to scan all the subdirs in the relative path
  429                               path = myfile.getFastRelativePath();
  430                           } else {
  431                               // may be on a case insensitive file system.  We want
  432                               // the results to show what's really on the disk, so
  433                               // we need to double check.
  434                               try {
  435                                   path = myfile.getRelativePath();
  436                                   traversesSymlinks = myfile.isTraverseSymlinks();
  437                               }  catch (IOException be) {
  438                                   throw new BuildException(be, getLocation());
  439                               } catch (BuildException be) {
  440                                   isOK = false;
  441                               }
  442                           }
  443                       } else {
  444                           isOK = false;
  445                       }
  446                       if (isOK) {
  447                           currentelement = path.replace(remoteFileSep.charAt(0), File.separatorChar);
  448                           if (!isFollowSymlinks()
  449                               && traversesSymlinks) {
  450                               continue;
  451                           }
  452   
  453                           if (myfile.isDirectory()) {
  454                               if (isIncluded(currentelement)
  455                                   && currentelement.length() > 0) {
  456                                   accountForIncludedDir(currentelement, myfile, true);
  457                               }  else {
  458                                   if (currentelement.length() > 0) {
  459                                       if (currentelement.charAt(currentelement
  460                                                                 .length() - 1)
  461                                           != File.separatorChar) {
  462                                           currentelement =
  463                                               currentelement + File.separatorChar;
  464                                       }
  465                                   }
  466                                   scandir(myfile.getAbsolutePath(), currentelement, true);
  467                               }
  468                           } else {
  469                               if (isCaseSensitive
  470                                   && originalpattern.equals(currentelement)) {
  471                                   accountForIncludedFile(currentelement);
  472                               } else if (!isCaseSensitive
  473                                          && originalpattern
  474                                          .equalsIgnoreCase(currentelement)) {
  475                                   accountForIncludedFile(currentelement);
  476                               }
  477                           }
  478                       }
  479                   }
  480               }
  481           }
  482           /**
  483            * scans a particular directory. populates the scannedDirs cache.
  484            *
  485            * @param dir directory to scan
  486            * @param vpath  relative path to the base directory of the remote fileset
  487            * always ended with a File.separator
  488            * @param fast seems to be always true in practice
  489            */
  490           protected void scandir(String dir, String vpath, boolean fast) {
  491               // avoid double scanning of directories, can only happen in fast mode
  492               if (fast && hasBeenScanned(vpath)) {
  493                   return;
  494               }
  495               try {
  496                   if (!ftp.changeWorkingDirectory(dir)) {
  497                       return;
  498                   }
  499                   String completePath = null;
  500                   if (!vpath.equals("")) {
  501                       completePath = rootPath + remoteFileSep
  502                           + vpath.replace(File.separatorChar, remoteFileSep.charAt(0));
  503                   } else {
  504                       completePath = rootPath;
  505                   }
  506                   FTPFile[] newfiles = listFiles(completePath, false);
  507   
  508                   if (newfiles == null) {
  509                       ftp.changeToParentDirectory();
  510                       return;
  511                   }
  512                   for (int i = 0; i < newfiles.length; i++) {
  513                       FTPFile file = newfiles[i];
  514                       if (file != null
  515                           && !file.getName().equals(".")
  516                           && !file.getName().equals("..")) {
  517                           String name = vpath + file.getName();
  518                           scannedDirs.put(name, new FTPFileProxy(file));
  519                           if (isFunctioningAsDirectory(ftp, dir, file)) {
  520                               boolean slowScanAllowed = true;
  521                               if (!isFollowSymlinks() && file.isSymbolicLink()) {
  522                                   dirsExcluded.addElement(name);
  523                                   slowScanAllowed = false;
  524                               } else if (isIncluded(name)) {
  525                                   accountForIncludedDir(name,
  526                                                         new AntFTPFile(ftp, file, completePath) , fast);
  527                               } else {
  528                                   dirsNotIncluded.addElement(name);
  529                                   if (fast && couldHoldIncluded(name)) {
  530                                       scandir(file.getName(),
  531                                               name + File.separator, fast);
  532                                   }
  533                               }
  534                               if (!fast && slowScanAllowed) {
  535                                   scandir(file.getName(),
  536                                           name + File.separator, fast);
  537                               }
  538                           } else {
  539                               if (!isFollowSymlinks() && file.isSymbolicLink()) {
  540                                   filesExcluded.addElement(name);
  541                               } else if (isFunctioningAsFile(ftp, dir, file)) {
  542                                   accountForIncludedFile(name);
  543                               }
  544                           }
  545                       }
  546                   }
  547                   ftp.changeToParentDirectory();
  548               } catch (IOException e) {
  549                   throw new BuildException("Error while communicating with FTP "
  550                                            + "server: ", e);
  551               }
  552           }
  553           /**
  554            * process included file
  555            * @param name  path of the file relative to the directory of the fileset
  556            */
  557           private void accountForIncludedFile(String name) {
  558               if (!filesIncluded.contains(name)
  559                   && !filesExcluded.contains(name)) {
  560   
  561                   if (isIncluded(name)) {
  562                       if (!isExcluded(name)
  563                           && isSelected(name, (File) scannedDirs.get(name))) {
  564                           filesIncluded.addElement(name);
  565                       } else {
  566                           filesExcluded.addElement(name);
  567                       }
  568                   } else {
  569                       filesNotIncluded.addElement(name);
  570                   }
  571               }
  572           }
  573   
  574           /**
  575            *
  576            * @param name path of the directory relative to the directory of
  577            * the fileset
  578            * @param file directory as file
  579            * @param fast
  580            */
  581           private void accountForIncludedDir(String name, AntFTPFile file, boolean fast) {
  582               if (!dirsIncluded.contains(name)
  583                   && !dirsExcluded.contains(name)) {
  584   
  585                   if (!isExcluded(name)) {
  586                       if (fast) {
  587                           if (file.isSymbolicLink()) {
  588                               try {
  589                                   file.getClient().changeWorkingDirectory(file.curpwd);
  590                               } catch (IOException ioe) {
  591                                   throw new BuildException("could not change directory to curpwd");
  592                               }
  593                               scandir(file.getLink(),
  594                                       name + File.separator, fast);
  595                           } else {
  596                               try {
  597                                   file.getClient().changeWorkingDirectory(file.curpwd);
  598                               } catch (IOException ioe) {
  599                                   throw new BuildException("could not change directory to curpwd");
  600                               }
  601                               scandir(file.getName(),
  602                                       name + File.separator, fast);
  603                           }
  604                       }
  605                       dirsIncluded.addElement(name);
  606                   } else {
  607                       dirsExcluded.addElement(name);
  608                       if (fast && couldHoldIncluded(name)) {
  609                           try {
  610                               file.getClient().changeWorkingDirectory(file.curpwd);
  611                           } catch (IOException ioe) {
  612                               throw new BuildException("could not change directory to curpwd");
  613                           }
  614                           scandir(file.getName(),
  615                                   name + File.separator, fast);
  616                       }
  617                   }
  618               }
  619           }
  620           /**
  621            * temporary table to speed up the various scanning methods below
  622            *
  623            * @since Ant 1.6
  624            */
  625           private Map fileListMap = new HashMap();
  626           /**
  627            * List of all scanned directories.
  628            *
  629            * @since Ant 1.6
  630            */
  631   
  632           private Map scannedDirs = new HashMap();
  633   
  634           /**
  635            * Has the directory with the given path relative to the base
  636            * directory already been scanned?
  637            *
  638            * @since Ant 1.6
  639            */
  640           private boolean hasBeenScanned(String vpath) {
  641               return scannedDirs.containsKey(vpath);
  642           }
  643   
  644           /**
  645            * Clear internal caches.
  646            *
  647            * @since Ant 1.6
  648            */
  649           private void clearCaches() {
  650               fileListMap.clear();
  651               scannedDirs.clear();
  652           }
  653           /**
  654            * list the files present in one directory.
  655            * @param directory full path on the remote side
  656            * @param changedir if true change to directory directory before listing
  657            * @return array of FTPFile
  658            */
  659           public FTPFile[] listFiles(String directory, boolean changedir) {
  660               //getProject().log("listing files in directory " + directory, Project.MSG_DEBUG);
  661               String currentPath = directory;
  662               if (changedir) {
  663                   try {
  664                       boolean result = ftp.changeWorkingDirectory(directory);
  665                       if (!result) {
  666                           return null;
  667                       }
  668                       currentPath = ftp.printWorkingDirectory();
  669                   } catch (IOException ioe) {
  670                       throw new BuildException(ioe, getLocation());
  671                   }
  672               }
  673               if (fileListMap.containsKey(currentPath)) {
  674                   getProject().log("filelist map used in listing files", Project.MSG_DEBUG);
  675                   return ((FTPFile[]) fileListMap.get(currentPath));
  676               }
  677               FTPFile[] result = null;
  678               try {
  679                   result = ftp.listFiles();
  680               } catch (IOException ioe) {
  681                   throw new BuildException(ioe, getLocation());
  682               }
  683               fileListMap.put(currentPath, result);
  684               if (!remoteSensitivityChecked) {
  685                   checkRemoteSensitivity(result, directory);
  686               }
  687               return result;
  688           }
  689   
  690           private void forceRemoteSensitivityCheck() {
  691               if (!remoteSensitivityChecked) {
  692                   try {
  693                       checkRemoteSensitivity(ftp.listFiles(), ftp.printWorkingDirectory());
  694                   } catch (IOException ioe) {
  695                       throw new BuildException(ioe, getLocation());
  696                   }
  697               }
  698           }
  699           /**
  700            * cd into one directory and
  701            * list the files present in one directory.
  702            * @param directory full path on the remote side
  703            * @return array of FTPFile
  704            */
  705           public FTPFile[] listFiles(String directory) {
  706               return listFiles(directory, true);
  707           }
  708           private void checkRemoteSensitivity(FTPFile[] array, String directory) {
  709               if (array == null) {
  710                   return;
  711               }
  712               boolean candidateFound = false;
  713               String target = null;
  714               for (int icounter = 0; icounter < array.length; icounter++) {
  715                   if (array[icounter] != null && array[icounter].isDirectory()) {
  716                       if (!array[icounter].getName().equals(".")
  717                           && !array[icounter].getName().equals("..")) {
  718                           candidateFound = true;
  719                           target = fiddleName(array[icounter].getName());
  720                           getProject().log("will try to cd to "
  721                                            + target + " where a directory called " + array[icounter].getName()
  722                                            + " exists", Project.MSG_DEBUG);
  723                           for (int pcounter = 0; pcounter < array.length; pcounter++) {
  724                               if (array[pcounter] != null
  725                                   && pcounter != icounter
  726                                   && target.equals(array[pcounter].getName())) {
  727                                   candidateFound = false;
  728                               }
  729                           }
  730                           if (candidateFound) {
  731                               break;
  732                           }
  733                       }
  734                   }
  735               }
  736               if (candidateFound) {
  737                   try {
  738                       getProject().log("testing case sensitivity, attempting to cd to "
  739                                        + target, Project.MSG_DEBUG);
  740                       remoteSystemCaseSensitive  = !ftp.changeWorkingDirectory(target);
  741                   } catch (IOException ioe) {
  742                       remoteSystemCaseSensitive = true;
  743                   } finally {
  744                       try {
  745                           ftp.changeWorkingDirectory(directory);
  746                       } catch (IOException ioe) {
  747                           throw new BuildException(ioe, getLocation());
  748                       }
  749                   }
  750                   getProject().log("remote system is case sensitive : " + remoteSystemCaseSensitive,
  751                                    Project.MSG_VERBOSE);
  752                   remoteSensitivityChecked = true;
  753               }
  754           }
  755           private String fiddleName(String origin) {
  756               StringBuffer result = new StringBuffer();
  757               for (int icounter = 0; icounter < origin.length(); icounter++) {
  758                   if (Character.isLowerCase(origin.charAt(icounter))) {
  759                       result.append(Character.toUpperCase(origin.charAt(icounter)));
  760                   } else if (Character.isUpperCase(origin.charAt(icounter))) {
  761                       result.append(Character.toLowerCase(origin.charAt(icounter)));
  762                   } else {
  763                       result.append(origin.charAt(icounter));
  764                   }
  765               }
  766               return result.toString();
  767           }
  768           /**
  769            * an AntFTPFile is a representation of a remote file
  770            * @since Ant 1.6
  771            */
  772           protected class AntFTPFile {
  773               /**
  774                * ftp client
  775                */
  776               private FTPClient client;
  777               /**
  778                * parent directory of the file
  779                */
  780               private String curpwd;
  781               /**
  782                * the file itself
  783                */
  784               private FTPFile ftpFile;
  785               /**
  786                *
  787                */
  788               private AntFTPFile parent = null;
  789               private boolean relativePathCalculated = false;
  790               private boolean traversesSymlinks = false;
  791               private String relativePath = "";
  792               /**
  793                * constructor
  794                * @param client ftp client variable
  795                * @param ftpFile the file
  796                * @param curpwd absolute remote path where the file is found
  797                */
  798               public AntFTPFile(FTPClient client, FTPFile ftpFile, String curpwd) {
  799                   this.client = client;
  800                   this.ftpFile = ftpFile;
  801                   this.curpwd = curpwd;
  802               }
  803               /**
  804                * other constructor
  805                * @param parent the parent file
  806                * @param path  a relative path to the parent file
  807                */
  808               public AntFTPFile(AntFTPFile parent, String path) {
  809                   this.parent = parent;
  810                   this.client = parent.client;
  811                   Vector pathElements = SelectorUtils.tokenizePath(path);
  812                   try {
  813                       boolean result = this.client.changeWorkingDirectory(parent.getAbsolutePath());
  814                       //this should not happen, except if parent has been deleted by another process
  815                       if (!result) {
  816                           return;
  817                       }
  818                       this.curpwd = parent.getAbsolutePath();
  819                   } catch (IOException ioe) {
  820                       throw new BuildException("could not change working dir to "
  821                                                + parent.curpwd);
  822                   }
  823                   for (int fcount = 0; fcount < pathElements.size() - 1; fcount++) {
  824                       String currentPathElement = (String) pathElements.elementAt(fcount);
  825                       try {
  826                           boolean result = this.client.changeWorkingDirectory(currentPathElement);
  827                           if (!result && !isCaseSensitive()
  828                               && (remoteSystemCaseSensitive || !remoteSensitivityChecked)) {
  829                               currentPathElement = findPathElementCaseUnsensitive(this.curpwd,
  830                                                                                   currentPathElement);
  831                               if (currentPathElement == null) {
  832                                   return;
  833                               }
  834                           } else if (!result) {
  835                               return;
  836                           }
  837                           this.curpwd = this.curpwd + remoteFileSep
  838                               + currentPathElement;
  839                       } catch (IOException ioe) {
  840                           throw new BuildException("could not change working dir to "
  841                                                    + (String) pathElements.elementAt(fcount)
  842                                                    + " from " + this.curpwd);
  843                       }
  844   
  845                   }
  846                   String lastpathelement = (String) pathElements.elementAt(pathElements.size() - 1);
  847                   FTPFile [] theFiles = listFiles(this.curpwd);
  848                   this.ftpFile = getFile(theFiles, lastpathelement);
  849               }
  850               /**
  851                * find a file in a directory in case unsensitive way
  852                * @param parentPath        where we are
  853                * @param soughtPathElement what is being sought
  854                * @return                  the first file found or null if not found
  855                */
  856               private String findPathElementCaseUnsensitive(String parentPath,
  857                                                             String soughtPathElement) {
  858                   // we are already in the right path, so the second parameter
  859                   // is false
  860                   FTPFile[] theFiles = listFiles(parentPath, false);
  861                   if (theFiles == null) {
  862                       return null;
  863                   }
  864                   for (int icounter = 0; icounter < theFiles.length; icounter++) {
  865                       if (theFiles[icounter] != null
  866                           && theFiles[icounter].getName().equalsIgnoreCase(soughtPathElement)) {
  867                           return theFiles[icounter].getName();
  868                       }
  869                   }
  870                   return null;
  871               }
  872               /**
  873                * find out if the file exists
  874                * @return  true if the file exists
  875                */
  876               public boolean exists() {
  877                   return (ftpFile != null);
  878               }
  879               /**
  880                * if the file is a symbolic link, find out to what it is pointing
  881                * @return the target of the symbolic link
  882                */
  883               public String getLink() {
  884                   return ftpFile.getLink();
  885               }
  886               /**
  887                * get the name of the file
  888                * @return the name of the file
  889                */
  890               public String getName() {
  891                   return ftpFile.getName();
  892               }
  893               /**
  894                * find out the absolute path of the file
  895                * @return absolute path as string
  896                */
  897               public String getAbsolutePath() {
  898                   return curpwd + remoteFileSep + ftpFile.getName();
  899               }
  900               /**
  901                * find out the relative path assuming that the path used to construct
  902                * this AntFTPFile was spelled properly with regards to case.
  903                * This is OK on a case sensitive system such as UNIX
  904                * @return relative path
  905                */
  906               public String getFastRelativePath() {
  907                   String absPath = getAbsolutePath();
  908                   if (absPath.indexOf(rootPath + remoteFileSep) == 0) {
  909                       return absPath.substring(rootPath.length() + remoteFileSep.length());
  910                   }
  911                   return null;
  912               }
  913               /**
  914                * find out the relative path to the rootPath of the enclosing scanner.
  915                * this relative path is spelled exactly like on disk,
  916                * for instance if the AntFTPFile has been instantiated as ALPHA,
  917                * but the file is really called alpha, this method will return alpha.
  918                * If a symbolic link is encountered, it is followed, but the name of the link
  919                * rather than the name of the target is returned.
  920                * (ie does not behave like File.getCanonicalPath())
  921                * @return                relative path, separated by remoteFileSep
  922                * @throws IOException    if a change directory fails, ...
  923                * @throws BuildException if one of the components of the relative path cannot
  924                * be found.
  925                */
  926               public String getRelativePath() throws IOException, BuildException {
  927                   if (!relativePathCalculated) {
  928                       if (parent != null) {
  929                           traversesSymlinks = parent.isTraverseSymlinks();
  930                           relativePath = getRelativePath(parent.getAbsolutePath(),
  931                                                          parent.getRelativePath());
  932                       } else {
  933                           relativePath = getRelativePath(rootPath, "");
  934                           relativePathCalculated = true;
  935                       }
  936                   }
  937                   return relativePath;
  938               }
  939               /**
  940                * get thge relative path of this file
  941                * @param currentPath          base path
  942                * @param currentRelativePath  relative path of the base path with regards to remote dir
  943                * @return relative path
  944                */
  945               private String getRelativePath(String currentPath, String currentRelativePath) {
  946                   Vector pathElements = SelectorUtils.tokenizePath(getAbsolutePath(), remoteFileSep);
  947                   Vector pathElements2 = SelectorUtils.tokenizePath(currentPath, remoteFileSep);
  948                   String relPath = currentRelativePath;
  949                   for (int pcount = pathElements2.size(); pcount < pathElements.size(); pcount++) {
  950                       String currentElement = (String) pathElements.elementAt(pcount);
  951                       FTPFile[] theFiles = listFiles(currentPath);
  952                       FTPFile theFile = null;
  953                       if (theFiles != null) {
  954                           theFile = getFile(theFiles, currentElement);
  955                       }
  956                       if (!relPath.equals("")) {
  957                           relPath = relPath + remoteFileSep;
  958                       }
  959                       if (theFile == null) {
  960                           // hit a hidden file assume not a symlink
  961                           relPath = relPath + currentElement;
  962                           currentPath = currentPath + remoteFileSep + currentElement;
  963                           log("Hidden file " + relPath
  964                               + " assumed to not be a symlink.",
  965                               Project.MSG_VERBOSE);
  966                       } else {
  967                           traversesSymlinks = traversesSymlinks || theFile.isSymbolicLink();
  968                           relPath = relPath + theFile.getName();
  969                           currentPath = currentPath + remoteFileSep + theFile.getName();
  970                       }
  971                   }
  972                   return relPath;
  973               }
  974               /**
  975                * find a file matching a string in an array of FTPFile.
  976                * This method will find "alpha" when requested for "ALPHA"
  977                * if and only if the caseSensitive attribute is set to false.
  978                * When caseSensitive is set to true, only the exact match is returned.
  979                * @param theFiles  array of files
  980                * @param lastpathelement  the file name being sought
  981                * @return null if the file cannot be found, otherwise return the matching file.
  982                */
  983               public FTPFile getFile(FTPFile[] theFiles, String lastpathelement) {
  984                   if (theFiles == null) {
  985                       return null;
  986                   }
  987                   for (int fcount = 0; fcount < theFiles.length; fcount++) {
  988                       if (theFiles[fcount] != null) {
  989                           if (theFiles[fcount].getName().equals(lastpathelement)) {
  990                               return theFiles[fcount];
  991                           } else if (!isCaseSensitive()
  992                                      && theFiles[fcount].getName().equalsIgnoreCase(
  993                                                                                     lastpathelement)) {
  994                               return theFiles[fcount];
  995                           }
  996                       }
  997                   }
  998                   return null;
  999               }
 1000               /**
 1001                * tell if a file is a directory.
 1002                * note that it will return false for symbolic links pointing to directories.
 1003                * @return <code>true</code> for directories
 1004                */
 1005               public boolean isDirectory() {
 1006                   return ftpFile.isDirectory();
 1007               }
 1008               /**
 1009                * tell if a file is a symbolic link
 1010                * @return <code>true</code> for symbolic links
 1011                */
 1012               public boolean isSymbolicLink() {
 1013                   return ftpFile.isSymbolicLink();
 1014               }
 1015               /**
 1016                * return the attached FTP client object.
 1017                * Warning : this instance is really shared with the enclosing class.
 1018                * @return  FTP client
 1019                */
 1020               protected FTPClient getClient() {
 1021                   return client;
 1022               }
 1023   
 1024               /**
 1025                * sets the current path of an AntFTPFile
 1026                * @param curpwd the current path one wants to set
 1027                */
 1028               protected void setCurpwd(String curpwd) {
 1029                   this.curpwd = curpwd;
 1030               }
 1031               /**
 1032                * returns the path of the directory containing the AntFTPFile.
 1033                * of the full path of the file itself in case of AntFTPRootFile
 1034                * @return parent directory of the AntFTPFile
 1035                */
 1036               public String getCurpwd() {
 1037                   return curpwd;
 1038               }
 1039               /**
 1040                * find out if a symbolic link is encountered in the relative path of this file
 1041                * from rootPath.
 1042                * @return <code>true</code> if a symbolic link is encountered in the relative path.
 1043                * @throws IOException if one of the change directory or directory listing operations
 1044                * fails
 1045                * @throws BuildException if a path component in the relative path cannot be found.
 1046                */
 1047               public boolean isTraverseSymlinks() throws IOException, BuildException {
 1048                   if (!relativePathCalculated) {
 1049                       // getRelativePath also finds about symlinks
 1050                       getRelativePath();
 1051                   }
 1052                   return traversesSymlinks;
 1053               }
 1054   
 1055               /**
 1056                * Get a string rep of this object.
 1057                * @return a string containing the pwd and the file.
 1058                */
 1059               public String toString() {
 1060                   return "AntFtpFile: " + curpwd + "%" + ftpFile;
 1061               }
 1062           }
 1063           /**
 1064            * special class to represent the remote directory itself
 1065            * @since Ant 1.6
 1066            */
 1067           protected class AntFTPRootFile extends AntFTPFile {
 1068               private String remotedir;
 1069               /**
 1070                * constructor
 1071                * @param aclient FTP client
 1072                * @param remotedir remote directory
 1073                */
 1074               public AntFTPRootFile(FTPClient aclient, String remotedir) {
 1075                   super(aclient, null, remotedir);
 1076                   this.remotedir = remotedir;
 1077                   try {
 1078                       this.getClient().changeWorkingDirectory(this.remotedir);
 1079                       this.setCurpwd(this.getClient().printWorkingDirectory());
 1080                   } catch (IOException ioe) {
 1081                       throw new BuildException(ioe, getLocation());
 1082                   }
 1083               }
 1084               /**
 1085                * find the absolute path
 1086                * @return absolute path
 1087                */
 1088               public String getAbsolutePath() {
 1089                   return this.getCurpwd();
 1090               }
 1091               /**
 1092                * find out the relative path to root
 1093                * @return empty string
 1094                * @throws BuildException actually never
 1095                * @throws IOException  actually never
 1096                */
 1097               public String getRelativePath() throws BuildException, IOException {
 1098                   return "";
 1099               }
 1100           }
 1101       }
 1102       /**
 1103        * check FTPFiles to check whether they function as directories too
 1104        * the FTPFile API seem to make directory and symbolic links incompatible
 1105        * we want to find out if we can cd to a symbolic link
 1106        * @param dir  the parent directory of the file to test
 1107        * @param file the file to test
 1108        * @return true if it is possible to cd to this directory
 1109        * @since ant 1.6
 1110        */
 1111       private boolean isFunctioningAsDirectory(FTPClient ftp, String dir, FTPFile file) {
 1112           boolean result = false;
 1113           String currentWorkingDir = null;
 1114           if (file.isDirectory()) {
 1115               return true;
 1116           } else if (file.isFile()) {
 1117               return false;
 1118           }
 1119           try {
 1120               currentWorkingDir = ftp.printWorkingDirectory();
 1121           } catch (IOException ioe) {
 1122               getProject().log("could not find current working directory " + dir
 1123                                + " while checking a symlink",
 1124                                Project.MSG_DEBUG);
 1125           }
 1126           if (currentWorkingDir != null) {
 1127               try {
 1128                   result = ftp.changeWorkingDirectory(file.getLink());
 1129               } catch (IOException ioe) {
 1130                   getProject().log("could not cd to " + file.getLink() + " while checking a symlink",
 1131                                    Project.MSG_DEBUG);
 1132               }
 1133               if (result) {
 1134                   boolean comeback = false;
 1135                   try {
 1136                       comeback = ftp.changeWorkingDirectory(currentWorkingDir);
 1137                   } catch (IOException ioe) {
 1138                       getProject().log("could not cd back to " + dir + " while checking a symlink",
 1139                                        Project.MSG_ERR);
 1140                   } finally {
 1141                       if (!comeback) {
 1142                           throw new BuildException("could not cd back to " + dir
 1143                                                    + " while checking a symlink");
 1144                       }
 1145                   }
 1146               }
 1147           }
 1148           return result;
 1149       }
 1150       /**
 1151        * check FTPFiles to check whether they function as directories too
 1152        * the FTPFile API seem to make directory and symbolic links incompatible
 1153        * we want to find out if we can cd to a symbolic link
 1154        * @param dir  the parent directory of the file to test
 1155        * @param file the file to test
 1156        * @return true if it is possible to cd to this directory
 1157        * @since ant 1.6
 1158        */
 1159       private boolean isFunctioningAsFile(FTPClient ftp, String dir, FTPFile file) {
 1160           if (file.isDirectory()) {
 1161               return false;
 1162           } else if (file.isFile()) {
 1163               return true;
 1164           }
 1165           return !isFunctioningAsDirectory(ftp, dir, file);
 1166       }
 1167       /**
 1168        * Sets the remote directory where files will be placed. This may be a
 1169        * relative or absolute path, and must be in the path syntax expected by
 1170        * the remote server. No correction of path syntax will be performed.
 1171        *
 1172        * @param dir the remote directory name.
 1173        */
 1174       public void setRemotedir(String dir) {
 1175           this.remotedir = dir;
 1176       }
 1177   
 1178   
 1179       /**
 1180        * Sets the FTP server to send files to.
 1181        *
 1182        * @param server the remote server name.
 1183        */
 1184       public void setServer(String server) {
 1185           this.server = server;
 1186       }
 1187   
 1188   
 1189       /**
 1190        * Sets the FTP port used by the remote server.
 1191        *
 1192        * @param port the port on which the remote server is listening.
 1193        */
 1194       public void setPort(int port) {
 1195           this.port = port;
 1196       }
 1197   
 1198   
 1199       /**
 1200        * Sets the login user id to use on the specified server.
 1201        *
 1202        * @param userid remote system userid.
 1203        */
 1204       public void setUserid(String userid) {
 1205           this.userid = userid;
 1206       }
 1207   
 1208   
 1209       /**
 1210        * Sets the login password for the given user id.
 1211        *
 1212        * @param password the password on the remote system.
 1213        */
 1214       public void setPassword(String password) {
 1215           this.password = password;
 1216       }
 1217   
 1218       /**
 1219        * Sets the login account to use on the specified server.
 1220        *
 1221        * @param pAccount the account name on remote system
 1222        * @since Ant 1.7
 1223        */
 1224       public void setAccount(String pAccount) {
 1225           this.account = pAccount;
 1226       }
 1227   
 1228   
 1229       /**
 1230        * If true, uses binary mode, otherwise text mode (default is binary).
 1231        *
 1232        * @param binary if true use binary mode in transfers.
 1233        */
 1234       public void setBinary(boolean binary) {
 1235           this.binary = binary;
 1236       }
 1237   
 1238   
 1239       /**
 1240        * Specifies whether to use passive mode. Set to true if you are behind a
 1241        * firewall and cannot connect without it. Passive mode is disabled by
 1242        * default.
 1243        *
 1244        * @param passive true is passive mode should be used.
 1245        */
 1246       public void setPassive(boolean passive) {
 1247           this.passive = passive;
 1248       }
 1249   
 1250   
 1251       /**
 1252        * Set to true to receive notification about each file as it is
 1253        * transferred.
 1254        *
 1255        * @param verbose true if verbose notifications are required.
 1256        */
 1257       public void setVerbose(boolean verbose) {
 1258           this.verbose = verbose;
 1259       }
 1260   
 1261   
 1262       /**
 1263        * A synonym for <tt>depends</tt>. Set to true to transmit only new
 1264        * or changed files.
 1265        *
 1266        * See the related attributes timediffmillis and timediffauto.
 1267        *
 1268        * @param newer if true only transfer newer files.
 1269        */
 1270       public void setNewer(boolean newer) {
 1271           this.newerOnly = newer;
 1272       }
 1273   
 1274       /**
 1275        * number of milliseconds to add to the time on the remote machine
 1276        * to get the time on the local machine.
 1277        *
 1278        * use in conjunction with <code>newer</code>
 1279        *
 1280        * @param timeDiffMillis number of milliseconds
 1281        *
 1282        * @since ant 1.6
 1283        */
 1284       public void setTimeDiffMillis(long timeDiffMillis) {
 1285           this.timeDiffMillis = timeDiffMillis;
 1286       }
 1287   
 1288       /**
 1289        * &quot;true&quot; to find out automatically the time difference
 1290        * between local and remote machine.
 1291        *
 1292        * This requires right to create
 1293        * and delete a temporary file in the remote directory.
 1294        *
 1295        * @param timeDiffAuto true = find automatically the time diff
 1296        *
 1297        * @since ant 1.6
 1298        */
 1299       public void setTimeDiffAuto(boolean timeDiffAuto) {
 1300           this.timeDiffAuto = timeDiffAuto;
 1301       }
 1302   
 1303       /**
 1304        * Set to true to preserve modification times for "gotten" files.
 1305        *
 1306        * @param preserveLastModified if true preserver modification times.
 1307        */
 1308       public void setPreserveLastModified(boolean preserveLastModified) {
 1309           this.preserveLastModified = preserveLastModified;
 1310       }
 1311   
 1312   
 1313       /**
 1314        * Set to true to transmit only files that are new or changed from their
 1315        * remote counterparts. The default is to transmit all files.
 1316        *
 1317        * @param depends if true only transfer newer files.
 1318        */
 1319       public void setDepends(boolean depends) {
 1320           this.newerOnly = depends;
 1321       }
 1322   
 1323   
 1324       /**
 1325        * Sets the remote file separator character. This normally defaults to the
 1326        * Unix standard forward slash, but can be manually overridden using this
 1327        * call if the remote server requires some other separator. Only the first
 1328        * character of the string is used.
 1329        *
 1330        * @param separator the file separator on the remote system.
 1331        */
 1332       public void setSeparator(String separator) {
 1333           remoteFileSep = separator;
 1334       }
 1335   
 1336   
 1337       /**
 1338        * Sets the file permission mode (Unix only) for files sent to the
 1339        * server.
 1340        *
 1341        * @param theMode unix style file mode for the files sent to the remote
 1342        *        system.
 1343        */
 1344       public void setChmod(String theMode) {
 1345           this.chmod = theMode;
 1346       }
 1347   
 1348   
 1349       /**
 1350        * Sets the default mask for file creation on a unix server.
 1351        *
 1352        * @param theUmask unix style umask for files created on the remote server.
 1353        */
 1354       public void setUmask(String theUmask) {
 1355           this.umask = theUmask;
 1356       }
 1357   
 1358   
 1359       /**
 1360        *  A set of files to upload or download
 1361        *
 1362        * @param set the set of files to be added to the list of files to be
 1363        *        transferred.
 1364        */
 1365       public void addFileset(FileSet set) {
 1366           filesets.addElement(set);
 1367       }
 1368   
 1369   
 1370       /**
 1371        * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
 1372        * "mkdir", "chmod", "list", and "site".
 1373        *
 1374        * @deprecated since 1.5.x.
 1375        *             setAction(String) is deprecated and is replaced with
 1376        *      setAction(FTP.Action) to make Ant's Introspection mechanism do the
 1377        *      work and also to encapsulate operations on the type in its own
 1378        *      class.
 1379        * @ant.attribute ignore="true"
 1380        *
 1381        * @param action the FTP action to be performed.
 1382        *
 1383        * @throws BuildException if the action is not a valid action.
 1384        */
 1385       public void setAction(String action) throws BuildException {
 1386           log("DEPRECATED - The setAction(String) method has been deprecated."
 1387               + " Use setAction(FTP.Action) instead.");
 1388   
 1389           Action a = new Action();
 1390   
 1391           a.setValue(action);
 1392           this.action = a.getAction();
 1393       }
 1394   
 1395   
 1396       /**
 1397        * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
 1398        * "mkdir", "chmod", "list", and "site".
 1399        *
 1400        * @param action the FTP action to be performed.
 1401        *
 1402        * @throws BuildException if the action is not a valid action.
 1403        */
 1404       public void setAction(Action action) throws BuildException {
 1405           this.action = action.getAction();
 1406       }
 1407   
 1408   
 1409       /**
 1410        * The output file for the "list" action. This attribute is ignored for
 1411        * any other actions.
 1412        *
 1413        * @param listing file in which to store the listing.
 1414        */
 1415       public void setListing(File listing) {
 1416           this.listing = listing;
 1417       }
 1418   
 1419   
 1420       /**
 1421        * If true, enables unsuccessful file put, delete and get
 1422        * operations to be skipped with a warning and the remainder
 1423        * of the files still transferred.
 1424        *
 1425        * @param skipFailedTransfers true if failures in transfers are ignored.
 1426        */
 1427       public void setSkipFailedTransfers(boolean skipFailedTransfers) {
 1428           this.skipFailedTransfers = skipFailedTransfers;
 1429       }
 1430   
 1431   
 1432       /**
 1433        * set the flag to skip errors on directory creation.
 1434        * (and maybe later other server specific errors)
 1435        *
 1436        * @param ignoreNoncriticalErrors true if non-critical errors should not
 1437        *        cause a failure.
 1438        */
 1439       public void setIgnoreNoncriticalErrors(boolean ignoreNoncriticalErrors) {
 1440           this.ignoreNoncriticalErrors = ignoreNoncriticalErrors;
 1441       }
 1442   
 1443       private void configurationHasBeenSet() {
 1444           this.isConfigurationSet = true;
 1445       }
 1446   
 1447       /**
 1448        * Sets the systemTypeKey attribute.
 1449        * Method for setting <code>FTPClientConfig</code> remote system key.
 1450        *
 1451        * @param systemKey the key to be set - BUT if blank
 1452        * the default value of null (which signifies "autodetect") will be kept.
 1453        * @see org.apache.commons.net.ftp.FTPClientConfig
 1454        */
 1455       public void setSystemTypeKey(FTPSystemType systemKey) {
 1456           if (systemKey != null && !systemKey.getValue().equals("")) {
 1457               this.systemTypeKey = systemKey;
 1458               configurationHasBeenSet();
 1459           }
 1460       }
 1461   
 1462       /**
 1463        * Sets the defaultDateFormatConfig attribute.
 1464        * @param defaultDateFormat configuration to be set, unless it is
 1465        * null or empty string, in which case ignored.
 1466        * @see org.apache.commons.net.ftp.FTPClientConfig
 1467        */
 1468       public void setDefaultDateFormatConfig(String defaultDateFormat) {
 1469           if (defaultDateFormat != null && !defaultDateFormat.equals("")) {
 1470               this.defaultDateFormatConfig = defaultDateFormat;
 1471               configurationHasBeenSet();
 1472           }
 1473       }
 1474   
 1475       /**
 1476        * Sets the recentDateFormatConfig attribute.
 1477        * @param recentDateFormat configuration to be set, unless it is
 1478        * null or empty string, in which case ignored.
 1479        * @see org.apache.commons.net.ftp.FTPClientConfig
 1480        */
 1481       public void setRecentDateFormatConfig(String recentDateFormat) {
 1482           if (recentDateFormat != null && !recentDateFormat.equals("")) {
 1483               this.recentDateFormatConfig = recentDateFormat;
 1484               configurationHasBeenSet();
 1485           }
 1486       }
 1487   
 1488       /**
 1489        * Sets the serverLanguageCode attribute.
 1490        * @param serverLanguageCode configuration to be set, unless it is
 1491        * null or empty string, in which case ignored.
 1492        * @see org.apache.commons.net.ftp.FTPClientConfig
 1493        */
 1494       public void setServerLanguageCodeConfig(LanguageCode serverLanguageCode) {
 1495           if (serverLanguageCode != null && !"".equals(serverLanguageCode.getValue())) {
 1496               this.serverLanguageCodeConfig = serverLanguageCode;
 1497               configurationHasBeenSet();
 1498           }
 1499       }
 1500   
 1501       /**
 1502        * Sets the serverTimeZoneConfig attribute.
 1503        * @param serverTimeZoneId configuration to be set, unless it is
 1504        * null or empty string, in which case ignored.
 1505        * @see org.apache.commons.net.ftp.FTPClientConfig
 1506        */
 1507       public void setServerTimeZoneConfig(String serverTimeZoneId) {
 1508           if (serverTimeZoneId != null && !serverTimeZoneId.equals("")) {
 1509               this.serverTimeZoneConfig = serverTimeZoneId;
 1510               configurationHasBeenSet();
 1511           }
 1512       }
 1513   
 1514       /**
 1515        * Sets the shortMonthNamesConfig attribute
 1516        *
 1517        * @param shortMonthNames configuration to be set, unless it is
 1518        * null or empty string, in which case ignored.
 1519        * @see org.apache.commons.net.ftp.FTPClientConfig
 1520        */
 1521       public void setShortMonthNamesConfig(String shortMonthNames) {
 1522           if (shortMonthNames != null && !shortMonthNames.equals("")) {
 1523               this.shortMonthNamesConfig = shortMonthNames;
 1524               configurationHasBeenSet();
 1525           }
 1526       }
 1527   
 1528   
 1529   
 1530       /**
 1531        * Defines how many times to retry executing FTP command before giving up.
 1532        * Default is 0 - try once and if failure then give up.
 1533        *
 1534        * @param retriesAllowed number of retries to allow.  -1 means
 1535        * keep trying forever. "forever" may also be specified as a
 1536        * synonym for -1.
 1537        */
 1538       public void setRetriesAllowed(String retriesAllowed) {
 1539           if ("FOREVER".equalsIgnoreCase(retriesAllowed)) {
 1540               this.retriesAllowed = Retryable.RETRY_FOREVER;
 1541           } else {
 1542               try {
 1543                   int retries = Integer.parseInt(retriesAllowed);
 1544                   if (retries < Retryable.RETRY_FOREVER) {
 1545                       throw new BuildException(
 1546                                                "Invalid value for retriesAllowed attribute: "
 1547                                                + retriesAllowed);
 1548   
 1549                   }
 1550                   this.retriesAllowed = retries;
 1551               } catch (NumberFormatException px) {
 1552                   throw new BuildException(
 1553                                            "Invalid value for retriesAllowed attribute: "
 1554                                            + retriesAllowed);
 1555   
 1556               }
 1557   
 1558           }
 1559       }
 1560       /**
 1561        * @return Returns the systemTypeKey.
 1562        */
 1563       public String getSystemTypeKey() {
 1564           return systemTypeKey.getValue();
 1565       }
 1566       /**
 1567        * @return Returns the defaultDateFormatConfig.
 1568        */
 1569       public String getDefaultDateFormatConfig() {
 1570           return defaultDateFormatConfig;
 1571       }
 1572       /**
 1573        * @return Returns the recentDateFormatConfig.
 1574        */
 1575       public String getRecentDateFormatConfig() {
 1576           return recentDateFormatConfig;
 1577       }
 1578       /**
 1579        * @return Returns the serverLanguageCodeConfig.
 1580        */
 1581       public String getServerLanguageCodeConfig() {
 1582           return serverLanguageCodeConfig.getValue();
 1583       }
 1584       /**
 1585        * @return Returns the serverTimeZoneConfig.
 1586        */
 1587       public String getServerTimeZoneConfig() {
 1588           return serverTimeZoneConfig;
 1589       }
 1590       /**
 1591        * @return Returns the shortMonthNamesConfig.
 1592        */
 1593       public String getShortMonthNamesConfig() {
 1594           return shortMonthNamesConfig;
 1595       }
 1596       /**
 1597        * @return Returns the timestampGranularity.
 1598        */
 1599       Granularity getTimestampGranularity() {
 1600           return timestampGranularity;
 1601       }
 1602       /**
 1603        * Sets the timestampGranularity attribute
 1604        * @param timestampGranularity The timestampGranularity to set.
 1605        */
 1606       public void setTimestampGranularity(Granularity timestampGranularity) {
 1607           if (null == timestampGranularity || "".equals(timestampGranularity.getValue())) {
 1608               return;
 1609           }
 1610           this.timestampGranularity = timestampGranularity;
 1611       }
 1612       /**
 1613        * Sets the siteCommand attribute.  This attribute
 1614        * names the command that will be executed if the action
 1615        * is "site".
 1616        * @param siteCommand The siteCommand to set.
 1617        */
 1618       public void setSiteCommand(String siteCommand) {
 1619           this.siteCommand = siteCommand;
 1620       }
 1621       /**
 1622        * Sets the initialSiteCommand attribute.  This attribute
 1623        * names a site command that will be executed immediately
 1624        * after connection.
 1625        * @param initialCommand The initialSiteCommand to set.
 1626        */
 1627       public void setInitialSiteCommand(String initialCommand) {
 1628           this.initialSiteCommand = initialCommand;
 1629       }
 1630   
 1631       /**
 1632        * Whether to verify that data and control connections are
 1633        * connected to the same remote host.
 1634        *
 1635        * @since Ant 1.8.0
 1636        */
 1637       public void setEnableRemoteVerification(boolean b) {
 1638           enableRemoteVerification = b;
 1639       }
 1640   
 1641       /**
 1642        * Checks to see that all required parameters are set.
 1643        *
 1644        * @throws BuildException if the configuration is not valid.
 1645        */
 1646       protected void checkAttributes() throws BuildException {
 1647           if (server == null) {
 1648               throw new BuildException("server attribute must be set!");
 1649           }
 1650           if (userid == null) {
 1651               throw new BuildException("userid attribute must be set!");
 1652           }
 1653           if (password == null) {
 1654               throw new BuildException("password attribute must be set!");
 1655           }
 1656   
 1657           if ((action == LIST_FILES) && (listing == null)) {
 1658               throw new BuildException("listing attribute must be set for list "
 1659                                        + "action!");
 1660           }
 1661   
 1662           if (action == MK_DIR && remotedir == null) {
 1663               throw new BuildException("remotedir attribute must be set for "
 1664                                        + "mkdir action!");
 1665           }
 1666   
 1667           if (action == CHMOD && chmod == null) {
 1668               throw new BuildException("chmod attribute must be set for chmod "
 1669                                        + "action!");
 1670           }
 1671           if (action == SITE_CMD && siteCommand == null) {
 1672               throw new BuildException("sitecommand attribute must be set for site "
 1673                                        + "action!");
 1674           }
 1675   
 1676   
 1677           if (this.isConfigurationSet) {
 1678               try {
 1679                   Class.forName("org.apache.commons.net.ftp.FTPClientConfig");
 1680               } catch (ClassNotFoundException e) {
 1681                   throw new BuildException(
 1682                                            "commons-net.jar >= 1.4.0 is required for at least one"
 1683                                            + " of the attributes specified.");
 1684               }
 1685           }
 1686       }
 1687   
 1688       /**
 1689        * Executable a retryable object.
 1690        * @param h the retry hander.
 1691        * @param r the object that should be retried until it succeeds
 1692        *          or the number of retrys is reached.
 1693        * @param descr a description of the command that is being run.
 1694        * @throws IOException if there is a problem.
 1695        */
 1696       protected void executeRetryable(RetryHandler h, Retryable r, String descr)
 1697           throws IOException {
 1698           h.execute(r, descr);
 1699       }
 1700   
 1701   
 1702       /**
 1703        * For each file in the fileset, do the appropriate action: send, get,
 1704        * delete, or list.
 1705        *
 1706        * @param ftp the FTPClient instance used to perform FTP actions
 1707        * @param fs the fileset on which the actions are performed.
 1708        *
 1709        * @return the number of files to be transferred.
 1710        *
 1711        * @throws IOException if there is a problem reading a file
 1712        * @throws BuildException if there is a problem in the configuration.
 1713        */
 1714       protected int transferFiles(final FTPClient ftp, FileSet fs)
 1715           throws IOException, BuildException {
 1716           DirectoryScanner ds;
 1717           if (action == SEND_FILES) {
 1718               ds = fs.getDirectoryScanner(getProject());
 1719           } else {
 1720               ds = new FTPDirectoryScanner(ftp);
 1721               fs.setupDirectoryScanner(ds, getProject());
 1722               ds.setFollowSymlinks(fs.isFollowSymlinks());
 1723               ds.scan();
 1724           }
 1725   
 1726           String[] dsfiles = null;
 1727           if (action == RM_DIR) {
 1728               dsfiles = ds.getIncludedDirectories();
 1729           } else {
 1730               dsfiles = ds.getIncludedFiles();
 1731           }
 1732           String dir = null;
 1733   
 1734           if ((ds.getBasedir() == null)
 1735               && ((action == SEND_FILES) || (action == GET_FILES))) {
 1736               throw new BuildException("the dir attribute must be set for send "
 1737                                        + "and get actions");
 1738           } else {
 1739               if ((action == SEND_FILES) || (action == GET_FILES)) {
 1740                   dir = ds.getBasedir().getAbsolutePath();
 1741               }
 1742           }
 1743   
 1744           // If we are doing a listing, we need the output stream created now.
 1745           BufferedWriter bw = null;
 1746   
 1747           try {
 1748               if (action == LIST_FILES) {
 1749                   File pd = listing.getParentFile();
 1750   
 1751                   if (!pd.exists()) {
 1752                       pd.mkdirs();
 1753                   }
 1754                   bw = new BufferedWriter(new FileWriter(listing));
 1755               }
 1756               RetryHandler h = new RetryHandler(this.retriesAllowed, this);
 1757               if (action == RM_DIR) {
 1758                   // to remove directories, start by the end of the list
 1759                   // the trunk does not let itself be removed before the leaves
 1760                   for (int i = dsfiles.length - 1; i >= 0; i--) {
 1761                       final String dsfile = dsfiles[i];
 1762                       executeRetryable(h, new Retryable() {
 1763                               public void execute() throws IOException {
 1764                                   rmDir(ftp, dsfile);
 1765                               }
 1766                           }, dsfile);
 1767                   }
 1768               } else {
 1769                   final BufferedWriter fbw = bw;
 1770                   final String fdir = dir;
 1771                   if (this.newerOnly) {
 1772                       this.granularityMillis =
 1773                           this.timestampGranularity.getMilliseconds(action);
 1774                   }
 1775                   for (int i = 0; i < dsfiles.length; i++) {
 1776                       final String dsfile = dsfiles[i];
 1777                       executeRetryable(h, new Retryable() {
 1778                               public void execute() throws IOException {
 1779                                   switch (action) {
 1780                                   case SEND_FILES:
 1781                                       sendFile(ftp, fdir, dsfile);
 1782                                       break;
 1783                                   case GET_FILES:
 1784                                       getFile(ftp, fdir, dsfile);
 1785                                       break;
 1786                                   case DEL_FILES:
 1787                                       delFile(ftp, dsfile);
 1788                                       break;
 1789                                   case LIST_FILES:
 1790                                       listFile(ftp, fbw, dsfile);
 1791                                       break;
 1792                                   case CHMOD:
 1793                                       doSiteCommand(ftp, "chmod " + chmod
 1794                                                     + " " + resolveFile(dsfile));
 1795                                       transferred++;
 1796                                       break;
 1797                                   default:
 1798                                       throw new BuildException("unknown ftp action " + action);
 1799                                   }
 1800                               }
 1801                           }, dsfile);
 1802                   }
 1803               }
 1804           } finally {
 1805               FileUtils.close(bw);
 1806           }
 1807   
 1808           return dsfiles.length;
 1809       }
 1810   
 1811   
 1812       /**
 1813        * Sends all files specified by the configured filesets to the remote
 1814        * server.
 1815        *
 1816        * @param ftp the FTPClient instance used to perform FTP actions
 1817        *
 1818        * @throws IOException if there is a problem reading a file
 1819        * @throws BuildException if there is a problem in the configuration.
 1820        */
 1821       protected void transferFiles(FTPClient ftp)
 1822           throws IOException, BuildException {
 1823           transferred = 0;
 1824           skipped = 0;
 1825   
 1826           if (filesets.size() == 0) {
 1827               throw new BuildException("at least one fileset must be specified.");
 1828           } else {
 1829               // get files from filesets
 1830               for (int i = 0; i < filesets.size(); i++) {
 1831                   FileSet fs = (FileSet) filesets.elementAt(i);
 1832   
 1833                   if (fs != null) {
 1834                       transferFiles(ftp, fs);
 1835                   }
 1836               }
 1837           }
 1838   
 1839           log(transferred + " " + ACTION_TARGET_STRS[action] + " "
 1840               + COMPLETED_ACTION_STRS[action]);
 1841           if (skipped != 0) {
 1842               log(skipped + " " + ACTION_TARGET_STRS[action]
 1843                   + " were not successfully " + COMPLETED_ACTION_STRS[action]);
 1844           }
 1845       }
 1846   
 1847   
 1848       /**
 1849        * Correct a file path to correspond to the remote host requirements. This
 1850        * implementation currently assumes that the remote end can handle
 1851        * Unix-style paths with forward-slash separators. This can be overridden
 1852        * with the <code>separator</code> task parameter. No attempt is made to
 1853        * determine what syntax is appropriate for the remote host.
 1854        *
 1855        * @param file the remote file name to be resolved
 1856        *
 1857        * @return the filename as it will appear on the server.
 1858        */
 1859       protected String resolveFile(String file) {
 1860           return file.replace(System.getProperty("file.separator").charAt(0),
 1861                               remoteFileSep.charAt(0));
 1862       }
 1863   
 1864   
 1865       /**
 1866        * Creates all parent directories specified in a complete relative
 1867        * pathname. Attempts to create existing directories will not cause
 1868        * errors.
 1869        *
 1870        * @param ftp the FTP client instance to use to execute FTP actions on
 1871        *        the remote server.
 1872        * @param filename the name of the file whose parents should be created.
 1873        * @throws IOException under non documented circumstances
 1874        * @throws BuildException if it is impossible to cd to a remote directory
 1875        *
 1876        */
 1877       protected void createParents(FTPClient ftp, String filename)
 1878           throws IOException, BuildException {
 1879   
 1880           File dir = new File(filename);
 1881           if (dirCache.contains(dir)) {
 1882               return;
 1883           }
 1884   
 1885           Vector parents = new Vector();
 1886           String dirname;
 1887   
 1888           while ((dirname = dir.getParent()) != null) {
 1889               File checkDir = new File(dirname);
 1890               if (dirCache.contains(checkDir)) {
 1891                   break;
 1892               }
 1893               dir = checkDir;
 1894               parents.addElement(dir);
 1895           }
 1896   
 1897           // find first non cached dir
 1898           int i = parents.size() - 1;
 1899   
 1900           if (i >= 0) {
 1901               String cwd = ftp.printWorkingDirectory();
 1902               String parent = dir.getParent();
 1903               if (parent != null) {
 1904                   if (!ftp.changeWorkingDirectory(resolveFile(parent))) {
 1905                       throw new BuildException("could not change to "
 1906                                                + "directory: " + ftp.getReplyString());
 1907                   }
 1908               }
 1909   
 1910               while (i >= 0) {
 1911                   dir = (File) parents.elementAt(i--);
 1912                   // check if dir exists by trying to change into it.
 1913                   if (!ftp.changeWorkingDirectory(dir.getName())) {
 1914                       // could not change to it - try to create it
 1915                       log("creating remote directory "
 1916                           + resolveFile(dir.getPath()), Project.MSG_VERBOSE);
 1917                       if (!ftp.makeDirectory(dir.getName())) {
 1918                           handleMkDirFailure(ftp);
 1919                       }
 1920                       if (!ftp.changeWorkingDirectory(dir.getName())) {
 1921                           throw new BuildException("could not change to "
 1922                                                    + "directory: " + ftp.getReplyString());
 1923                       }
 1924                   }
 1925                   dirCache.add(dir);
 1926               }
 1927               ftp.changeWorkingDirectory(cwd);
 1928           }
 1929       }
 1930       /**
 1931        * auto find the time difference between local and remote
 1932        * @param ftp handle to ftp client
 1933        * @return number of millis to add to remote time to make it comparable to local time
 1934        * @since ant 1.6
 1935        */
 1936       private long getTimeDiff(FTPClient ftp) {
 1937           long returnValue = 0;
 1938           File tempFile = findFileName(ftp);
 1939           try {
 1940               // create a local temporary file
 1941               FILE_UTILS.createNewFile(tempFile);
 1942               long localTimeStamp = tempFile.lastModified();
 1943               BufferedInputStream instream = new BufferedInputStream(new FileInputStream(tempFile));
 1944               ftp.storeFile(tempFile.getName(), instream);
 1945               instream.close();
 1946               boolean success = FTPReply.isPositiveCompletion(ftp.getReplyCode());
 1947               if (success) {
 1948                   FTPFile [] ftpFiles = ftp.listFiles(tempFile.getName());
 1949                   if (ftpFiles.length == 1) {
 1950                       long remoteTimeStamp = ftpFiles[0].getTimestamp().getTime().getTime();
 1951                       returnValue = localTimeStamp - remoteTimeStamp;
 1952                   }
 1953                   ftp.deleteFile(ftpFiles[0].getName());
 1954               }
 1955               // delegate the deletion of the local temp file to the delete task
 1956               // because of race conditions occuring on Windows
 1957               Delete mydelete = new Delete();
 1958               mydelete.bindToOwner(this);
 1959               mydelete.setFile(tempFile.getCanonicalFile());
 1960               mydelete.execute();
 1961           } catch (Exception e) {
 1962               throw new BuildException(e, getLocation());
 1963           }
 1964           return returnValue;
 1965       }
 1966       /**
 1967        *  find a suitable name for local and remote temporary file
 1968        */
 1969       private File findFileName(FTPClient ftp) {
 1970           FTPFile [] theFiles = null;
 1971           final int maxIterations = 1000;
 1972           for (int counter = 1; counter < maxIterations; counter++) {
 1973               File localFile = FILE_UTILS.createTempFile(
 1974                                                          "ant" + Integer.toString(counter), ".tmp",
 1975                                                          null, false, false);
 1976               String fileName = localFile.getName();
 1977               boolean found = false;
 1978               try {
 1979                   if (theFiles == null) {
 1980                       theFiles = ftp.listFiles();
 1981                   }
 1982                   for (int counter2 = 0; counter2 < theFiles.length; counter2++) {
 1983                       if (theFiles[counter2] != null
 1984                           && theFiles[counter2].getName().equals(fileName)) {
 1985                           found = true;
 1986                           break;
 1987                       }
 1988                   }
 1989               } catch (IOException ioe) {
 1990                   throw new BuildException(ioe, getLocation());
 1991               }
 1992               if (!found) {
 1993                   localFile.deleteOnExit();
 1994                   return localFile;
 1995               }
 1996           }
 1997           return null;
 1998       }
 1999   
 2000       /**
 2001        * Checks to see if the remote file is current as compared with the local
 2002        * file. Returns true if the target file is up to date.
 2003        * @param ftp ftpclient
 2004        * @param localFile local file
 2005        * @param remoteFile remote file
 2006        * @return true if the target file is up to date
 2007        * @throws IOException  in unknown circumstances
 2008        * @throws BuildException if the date of the remote files cannot be found and the action is
 2009        * GET_FILES
 2010        */
 2011       protected boolean isUpToDate(FTPClient ftp, File localFile,
 2012                                    String remoteFile)
 2013           throws IOException, BuildException {
 2014           log("checking date for " + remoteFile, Project.MSG_VERBOSE);
 2015   
 2016           FTPFile[] files = ftp.listFiles(remoteFile);
 2017   
 2018           // For Microsoft's Ftp-Service an Array with length 0 is
 2019           // returned if configured to return listings in "MS-DOS"-Format
 2020           if (files == null || files.length == 0) {
 2021               // If we are sending files, then assume out of date.
 2022               // If we are getting files, then throw an error
 2023   
 2024               if (action == SEND_FILES) {
 2025                   log("Could not date test remote file: " + remoteFile
 2026                       + "assuming out of date.", Project.MSG_VERBOSE);
 2027                   return false;
 2028               } else {
 2029                   throw new BuildException("could not date test remote file: "
 2030                                            + ftp.getReplyString());
 2031               }
 2032           }
 2033   
 2034           long remoteTimestamp = files[0].getTimestamp().getTime().getTime();
 2035           long localTimestamp = localFile.lastModified();
 2036           long adjustedRemoteTimestamp =
 2037               remoteTimestamp + this.timeDiffMillis + this.granularityMillis;
 2038   
 2039           StringBuffer msg;
 2040           synchronized(TIMESTAMP_LOGGING_SDF) {
 2041               msg = new StringBuffer("   [")
 2042                   .append(TIMESTAMP_LOGGING_SDF.format(new Date(localTimestamp)))
 2043                   .append("] local");
 2044           }
 2045           log(msg.toString(), Project.MSG_VERBOSE);
 2046   
 2047           synchronized(TIMESTAMP_LOGGING_SDF) {
 2048               msg = new StringBuffer("   [")
 2049                   .append(TIMESTAMP_LOGGING_SDF.format(new Date(adjustedRemoteTimestamp)))
 2050                   .append("] remote");
 2051           }
 2052           if (remoteTimestamp != adjustedRemoteTimestamp) {
 2053               synchronized(TIMESTAMP_LOGGING_SDF) {
 2054                   msg.append(" - (raw: ")
 2055                       .append(TIMESTAMP_LOGGING_SDF.format(new Date(remoteTimestamp)))
 2056                       .append(")");
 2057               }
 2058           }
 2059           log(msg.toString(), Project.MSG_VERBOSE);
 2060   
 2061   
 2062   
 2063           if (this.action == SEND_FILES) {
 2064               return adjustedRemoteTimestamp >= localTimestamp;
 2065           } else {
 2066               return localTimestamp >= adjustedRemoteTimestamp;
 2067           }
 2068       }
 2069   
 2070   
 2071       /**
 2072        * Sends a site command to the ftp server
 2073        * @param ftp ftp client
 2074        * @param theCMD command to execute
 2075        * @throws IOException  in unknown circumstances
 2076        * @throws BuildException in unknown circumstances
 2077        */
 2078       protected void doSiteCommand(FTPClient ftp, String theCMD)
 2079           throws IOException, BuildException {
 2080           boolean rc;
 2081           String[] myReply = null;
 2082   
 2083           log("Doing Site Command: " + theCMD, Project.MSG_VERBOSE);
 2084   
 2085           rc = ftp.sendSiteCommand(theCMD);
 2086   
 2087           if (!rc) {
 2088               log("Failed to issue Site Command: " + theCMD, Project.MSG_WARN);
 2089           } else {
 2090   
 2091               myReply = ftp.getReplyStrings();
 2092   
 2093               for (int x = 0; x < myReply.length; x++) {
 2094                   if (myReply[x].indexOf("200") == -1) {
 2095                       log(myReply[x], Project.MSG_WARN);
 2096                   }
 2097               }
 2098           }
 2099       }
 2100   
 2101   
 2102       /**
 2103        * Sends a single file to the remote host. <code>filename</code> may
 2104        * contain a relative path specification. When this is the case, <code>sendFile</code>
 2105        * will attempt to create any necessary parent directories before sending
 2106        * the file. The file will then be sent using the entire relative path
 2107        * spec - no attempt is made to change directories. It is anticipated that
 2108        * this may eventually cause problems with some FTP servers, but it
 2109        * simplifies the coding.
 2110        * @param ftp ftp client
 2111        * @param dir base directory of the file to be sent (local)
 2112        * @param filename relative path of the file to be send
 2113        *        locally relative to dir
 2114        *        remotely relative to the remotedir attribute
 2115        * @throws IOException  in unknown circumstances
 2116        * @throws BuildException in unknown circumstances
 2117        */
 2118       protected void sendFile(FTPClient ftp, String dir, String filename)
 2119           throws IOException, BuildException {
 2120           InputStream instream = null;
 2121   
 2122           try {
 2123               // XXX - why not simply new File(dir, filename)?
 2124               File file = getProject().resolveFile(new File(dir, filename).getPath());
 2125   
 2126               if (newerOnly && isUpToDate(ftp, file, resolveFile(filename))) {
 2127                   return;
 2128               }
 2129   
 2130               if (verbose) {
 2131                   log("transferring " + file.getAbsolutePath());
 2132               }
 2133   
 2134               instream = new BufferedInputStream(new FileInputStream(file));
 2135   
 2136               createParents(ftp, filename);
 2137   
 2138               ftp.storeFile(resolveFile(filename), instream);
 2139   
 2140               boolean success = FTPReply.isPositiveCompletion(ftp.getReplyCode());
 2141   
 2142               if (!success) {
 2143                   String s = "could not put file: " + ftp.getReplyString();
 2144   
 2145                   if (skipFailedTransfers) {
 2146                       log(s, Project.MSG_WARN);
 2147                       skipped++;
 2148                   } else {
 2149                       throw new BuildException(s);
 2150                   }
 2151   
 2152               } else {
 2153                   // see if we should issue a chmod command
 2154                   if (chmod != null) {
 2155                       doSiteCommand(ftp, "chmod " + chmod + " " + resolveFile(filename));
 2156                   }
 2157                   log("File " + file.getAbsolutePath() + " copied to " + server,
 2158                       Project.MSG_VERBOSE);
 2159                   transferred++;
 2160               }
 2161           } finally {
 2162               FileUtils.close(instream);
 2163           }
 2164       }
 2165   
 2166   
 2167       /**
 2168        * Delete a file from the remote host.
 2169        * @param ftp ftp client
 2170        * @param filename file to delete
 2171        * @throws IOException  in unknown circumstances
 2172        * @throws BuildException if skipFailedTransfers is set to false
 2173        * and the deletion could not be done
 2174        */
 2175       protected void delFile(FTPClient ftp, String filename)
 2176           throws IOException, BuildException {
 2177           if (verbose) {
 2178               log("deleting " + filename);
 2179           }
 2180   
 2181           if (!ftp.deleteFile(resolveFile(filename))) {
 2182               String s = "could not delete file: " + ftp.getReplyString();
 2183   
 2184               if (skipFailedTransfers) {
 2185                   log(s, Project.MSG_WARN);
 2186                   skipped++;
 2187               } else {
 2188                   throw new BuildException(s);
 2189               }
 2190           } else {
 2191               log("File " + filename + " deleted from " + server,
 2192                   Project.MSG_VERBOSE);
 2193               transferred++;
 2194           }
 2195       }
 2196   
 2197       /**
 2198        * Delete a directory, if empty, from the remote host.
 2199        * @param ftp ftp client
 2200        * @param dirname directory to delete
 2201        * @throws IOException  in unknown circumstances
 2202        * @throws BuildException if skipFailedTransfers is set to false
 2203        * and the deletion could not be done
 2204        */
 2205       protected void rmDir(FTPClient ftp, String dirname)
 2206           throws IOException, BuildException {
 2207           if (verbose) {
 2208               log("removing " + dirname);
 2209           }
 2210   
 2211           if (!ftp.removeDirectory(resolveFile(dirname))) {
 2212               String s = "could not remove directory: " + ftp.getReplyString();
 2213   
 2214               if (skipFailedTransfers) {
 2215                   log(s, Project.MSG_WARN);
 2216                   skipped++;
 2217               } else {
 2218                   throw new BuildException(s);
 2219               }
 2220           } else {
 2221               log("Directory " + dirname + " removed from " + server,
 2222                   Project.MSG_VERBOSE);
 2223               transferred++;
 2224           }
 2225       }
 2226   
 2227   
 2228       /**
 2229        * Retrieve a single file from the remote host. <code>filename</code> may
 2230        * contain a relative path specification. <p>
 2231        *
 2232        * The file will then be retreived using the entire relative path spec -
 2233        * no attempt is made to change directories. It is anticipated that this
 2234        * may eventually cause problems with some FTP servers, but it simplifies
 2235        * the coding.</p>
 2236        * @param ftp the ftp client
 2237        * @param dir local base directory to which the file should go back
 2238        * @param filename relative path of the file based upon the ftp remote directory
 2239        *        and/or the local base directory (dir)
 2240        * @throws IOException  in unknown circumstances
 2241        * @throws BuildException if skipFailedTransfers is false
 2242        * and the file cannot be retrieved.
 2243        */
 2244       protected void getFile(FTPClient ftp, String dir, String filename)
 2245           throws IOException, BuildException {
 2246           OutputStream outstream = null;
 2247           try {
 2248               File file = getProject().resolveFile(new File(dir, filename).getPath());
 2249   
 2250               if (newerOnly && isUpToDate(ftp, file, resolveFile(filename))) {
 2251                   return;
 2252               }
 2253   
 2254               if (verbose) {
 2255                   log("transferring " + filename + " to "
 2256                       + file.getAbsolutePath());
 2257               }
 2258   
 2259               File pdir = file.getParentFile();
 2260   
 2261               if (!pdir.exists()) {
 2262                   pdir.mkdirs();
 2263               }
 2264               outstream = new BufferedOutputStream(new FileOutputStream(file));
 2265               ftp.retrieveFile(resolveFile(filename), outstream);
 2266   
 2267               if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
 2268                   String s = "could not get file: " + ftp.getReplyString();
 2269   
 2270                   if (skipFailedTransfers) {
 2271                       log(s, Project.MSG_WARN);
 2272                       skipped++;
 2273                   } else {
 2274                       throw new BuildException(s);
 2275                   }
 2276   
 2277               } else {
 2278                   log("File " + file.getAbsolutePath() + " copied from "
 2279                       + server, Project.MSG_VERBOSE);
 2280                   transferred++;
 2281                   if (preserveLastModified) {
 2282                       outstream.close();
 2283                       outstream = null;
 2284                       FTPFile[] remote = ftp.listFiles(resolveFile(filename));
 2285                       if (remote.length > 0) {
 2286                           FILE_UTILS.setFileLastModified(file,
 2287                                                          remote[0].getTimestamp()
 2288                                                          .getTime().getTime());
 2289                       }
 2290                   }
 2291               }
 2292           } finally {
 2293               FileUtils.close(outstream);
 2294           }
 2295       }
 2296   
 2297   
 2298       /**
 2299        * List information about a single file from the remote host. <code>filename</code>
 2300        * may contain a relative path specification. <p>
 2301        *
 2302        * The file listing will then be retrieved using the entire relative path
 2303        * spec - no attempt is made to change directories. It is anticipated that
 2304        * this may eventually cause problems with some FTP servers, but it
 2305        * simplifies the coding.</p>
 2306        * @param ftp ftp client
 2307        * @param bw buffered writer
 2308        * @param filename the directory one wants to list
 2309        * @throws IOException  in unknown circumstances
 2310        * @throws BuildException in unknown circumstances
 2311        */
 2312       protected void listFile(FTPClient ftp, BufferedWriter bw, String filename)
 2313           throws IOException, BuildException {
 2314           if (verbose) {
 2315               log("listing " + filename);
 2316           }
 2317           FTPFile[] ftpfiles = ftp.listFiles(resolveFile(filename));
 2318   
 2319           if (ftpfiles != null && ftpfiles.length > 0) {
 2320               bw.write(ftpfiles[0].toString());
 2321               bw.newLine();
 2322               transferred++;
 2323           }
 2324       }
 2325   
 2326   
 2327       /**
 2328        * Create the specified directory on the remote host.
 2329        *
 2330        * @param ftp The FTP client connection
 2331        * @param dir The directory to create (format must be correct for host
 2332        *      type)
 2333        * @throws IOException  in unknown circumstances
 2334        * @throws BuildException if ignoreNoncriticalErrors has not been set to true
 2335        *         and a directory could not be created, for instance because it was
 2336        *         already existing. Precisely, the codes 521, 550 and 553 will trigger
 2337        *         a BuildException
 2338        */
 2339       protected void makeRemoteDir(FTPClient ftp, String dir)
 2340           throws IOException, BuildException {
 2341           String workingDirectory = ftp.printWorkingDirectory();
 2342           if (verbose) {
 2343               if (dir.indexOf("/") == 0 || workingDirectory == null) {
 2344                   log("Creating directory: " + dir + " in /");
 2345               } else {
 2346                   log("Creating directory: " + dir + " in " + workingDirectory);
 2347               }
 2348           }
 2349           if (dir.indexOf("/") == 0) {
 2350               ftp.changeWorkingDirectory("/");
 2351           }
 2352           String subdir = "";
 2353           StringTokenizer st = new StringTokenizer(dir, "/");
 2354           while (st.hasMoreTokens()) {
 2355               subdir = st.nextToken();
 2356               log("Checking " + subdir, Project.MSG_DEBUG);
 2357               if (!ftp.changeWorkingDirectory(subdir)) {
 2358                   if (!ftp.makeDirectory(subdir)) {
 2359                       // codes 521, 550 and 553 can be produced by FTP Servers
 2360                       //  to indicate that an attempt to create a directory has
 2361                       //  failed because the directory already exists.
 2362                       int rc = ftp.getReplyCode();
 2363                       if (!(ignoreNoncriticalErrors
 2364                             && (rc == FTPReply.CODE_550 || rc == FTPReply.CODE_553
 2365                                 || rc == CODE_521))) {
 2366                           throw new BuildException("could not create directory: "
 2367                                                    + ftp.getReplyString());
 2368                       }
 2369                       if (verbose) {
 2370                           log("Directory already exists");
 2371                       }
 2372                   } else {
 2373                       if (verbose) {
 2374                           log("Directory created OK");
 2375                       }
 2376                       ftp.changeWorkingDirectory(subdir);
 2377                   }
 2378               }
 2379           }
 2380           if (workingDirectory != null) {
 2381               ftp.changeWorkingDirectory(workingDirectory);
 2382           }
 2383       }
 2384   
 2385       /**
 2386        * look at the response for a failed mkdir action, decide whether
 2387        * it matters or not. If it does, we throw an exception
 2388        * @param ftp current ftp connection
 2389        * @throws BuildException if this is an error to signal
 2390        */
 2391       private void handleMkDirFailure(FTPClient ftp)
 2392           throws BuildException {
 2393           int rc = ftp.getReplyCode();
 2394           if (!(ignoreNoncriticalErrors
 2395                 && (rc == FTPReply.CODE_550 || rc == FTPReply.CODE_553 || rc == CODE_521))) {
 2396               throw new BuildException("could not create directory: "
 2397                                        + ftp.getReplyString());
 2398           }
 2399       }
 2400   
 2401       /**
 2402        * Runs the task.
 2403        *
 2404        * @throws BuildException if the task fails or is not configured
 2405        *         correctly.
 2406        */
 2407       public void execute() throws BuildException {
 2408           checkAttributes();
 2409   
 2410           FTPClient ftp = null;
 2411   
 2412           try {
 2413               log("Opening FTP connection to " + server, Project.MSG_VERBOSE);
 2414   
 2415               ftp = new FTPClient();
 2416               if (this.isConfigurationSet) {
 2417                   ftp = FTPConfigurator.configure(ftp, this);
 2418               }
 2419   
 2420               ftp.setRemoteVerificationEnabled(enableRemoteVerification);
 2421               ftp.connect(server, port);
 2422               if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
 2423                   throw new BuildException("FTP connection failed: "
 2424                                            + ftp.getReplyString());
 2425               }
 2426   
 2427               log("connected", Project.MSG_VERBOSE);
 2428               log("logging in to FTP server", Project.MSG_VERBOSE);
 2429   
 2430               if ((this.account != null && !ftp.login(userid, password, account))
 2431                   || (this.account == null && !ftp.login(userid, password))) {
 2432                   throw new BuildException("Could not login to FTP server");
 2433               }
 2434   
 2435               log("login succeeded", Project.MSG_VERBOSE);
 2436   
 2437               if (binary) {
 2438                   ftp.setFileType(org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);
 2439                   if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
 2440                       throw new BuildException("could not set transfer type: "
 2441                                                + ftp.getReplyString());
 2442                   }
 2443               } else {
 2444                   ftp.setFileType(org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE);
 2445                   if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
 2446                       throw new BuildException("could not set transfer type: "
 2447                                                + ftp.getReplyString());
 2448                   }
 2449               }
 2450   
 2451               if (passive) {
 2452                   log("entering passive mode", Project.MSG_VERBOSE);
 2453                   ftp.enterLocalPassiveMode();
 2454                   if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
 2455                       throw new BuildException("could not enter into passive "
 2456                                                + "mode: " + ftp.getReplyString());
 2457                   }
 2458               }
 2459   
 2460               // If an initial command was configured then send it.
 2461               // Some FTP servers offer different modes of operation,
 2462               // E.G. switching between a UNIX file system mode and
 2463               // a legacy file system.
 2464               if (this.initialSiteCommand != null) {
 2465                   RetryHandler h = new RetryHandler(this.retriesAllowed, this);
 2466                   final FTPClient lftp = ftp;
 2467                   executeRetryable(h, new Retryable() {
 2468                           public void execute() throws IOException {
 2469                               doSiteCommand(lftp, FTP.this.initialSiteCommand);
 2470                           }
 2471                       }, "initial site command: " + this.initialSiteCommand);
 2472               }
 2473   
 2474   
 2475               // For a unix ftp server you can set the default mask for all files
 2476               // created.
 2477   
 2478               if (umask != null) {
 2479                   RetryHandler h = new RetryHandler(this.retriesAllowed, this);
 2480                   final FTPClient lftp = ftp;
 2481                   executeRetryable(h, new Retryable() {
 2482                           public void execute() throws IOException {
 2483                               doSiteCommand(lftp, "umask " + umask);
 2484                           }
 2485                       }, "umask " + umask);
 2486               }
 2487   
 2488               // If the action is MK_DIR, then the specified remote
 2489               // directory is the directory to create.
 2490   
 2491               if (action == MK_DIR) {
 2492                   RetryHandler h = new RetryHandler(this.retriesAllowed, this);
 2493                   final FTPClient lftp = ftp;
 2494                   executeRetryable(h, new Retryable() {
 2495                           public void execute() throws IOException {
 2496                               makeRemoteDir(lftp, remotedir);
 2497                           }
 2498                       }, remotedir);
 2499               } else if (action == SITE_CMD) {
 2500                   RetryHandler h = new RetryHandler(this.retriesAllowed, this);
 2501                   final FTPClient lftp = ftp;
 2502                   executeRetryable(h, new Retryable() {
 2503                           public void execute() throws IOException {
 2504                               doSiteCommand(lftp, FTP.this.siteCommand);
 2505                           }
 2506                       }, "Site Command: " + this.siteCommand);
 2507               } else {
 2508                   if (remotedir != null) {
 2509                       log("changing the remote directory to " + remotedir,
 2510                           Project.MSG_VERBOSE);
 2511                       ftp.changeWorkingDirectory(remotedir);
 2512                       if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
 2513                           throw new BuildException("could not change remote "
 2514                                                    + "directory: " + ftp.getReplyString());
 2515                       }
 2516                   }
 2517                   if (newerOnly && timeDiffAuto) {
 2518                       // in this case we want to find how much time span there is between local
 2519                       // and remote
 2520                       timeDiffMillis = getTimeDiff(ftp);
 2521                   }
 2522                   log(ACTION_STRS[action] + " " + ACTION_TARGET_STRS[action]);
 2523                   transferFiles(ftp);
 2524               }
 2525   
 2526           } catch (IOException ex) {
 2527               throw new BuildException("error during FTP transfer: " + ex, ex);
 2528           } finally {
 2529               if (ftp != null && ftp.isConnected()) {
 2530                   try {
 2531                       log("disconnecting", Project.MSG_VERBOSE);
 2532                       ftp.logout();
 2533                       ftp.disconnect();
 2534                   } catch (IOException ex) {
 2535                       // ignore it
 2536                   }
 2537               }
 2538           }
 2539       }
 2540   
 2541   
 2542       /**
 2543        * an action to perform, one of
 2544        * "send", "put", "recv", "get", "del", "delete", "list", "mkdir", "chmod",
 2545        * "rmdir"
 2546        */
 2547       public static class Action extends EnumeratedAttribute {
 2548   
 2549           private static final String[] VALID_ACTIONS = {
 2550               "send", "put", "recv", "get", "del", "delete", "list", "mkdir",
 2551               "chmod", "rmdir", "site"
 2552           };
 2553   
 2554   
 2555           /**
 2556            * Get the valid values
 2557            *
 2558            * @return an array of the valid FTP actions.
 2559            */
 2560           public String[] getValues() {
 2561               return VALID_ACTIONS;
 2562           }
 2563   
 2564   
 2565           /**
 2566            * Get the symbolic equivalent of the action value.
 2567            *
 2568            * @return the SYMBOL representing the given action.
 2569            */
 2570           public int getAction() {
 2571               String actionL = getValue().toLowerCase(Locale.ENGLISH);
 2572               if (actionL.equals("send") || actionL.equals("put")) {
 2573                   return SEND_FILES;
 2574               } else if (actionL.equals("recv") || actionL.equals("get")) {
 2575                   return GET_FILES;
 2576               } else if (actionL.equals("del") || actionL.equals("delete")) {
 2577                   return DEL_FILES;
 2578               } else if (actionL.equals("list")) {
 2579                   return LIST_FILES;
 2580               } else if (actionL.equals("chmod")) {
 2581                   return CHMOD;
 2582               } else if (actionL.equals("mkdir")) {
 2583                   return MK_DIR;
 2584               } else if (actionL.equals("rmdir")) {
 2585                   return RM_DIR;
 2586               } else if (actionL.equals("site")) {
 2587                   return SITE_CMD;
 2588               }
 2589               return SEND_FILES;
 2590           }
 2591       }
 2592       /**
 2593        * represents one of the valid timestamp adjustment values
 2594        * recognized by the <code>timestampGranularity</code> attribute.<p>
 2595   
 2596        * A timestamp adjustment may be used in file transfers for checking
 2597        * uptodateness. MINUTE means to add one minute to the server
 2598        * timestamp.  This is done because FTP servers typically list
 2599        * timestamps HH:mm and client FileSystems typically use HH:mm:ss.
 2600        *
 2601        * The default is to use MINUTE for PUT actions and NONE for GET
 2602        * actions, since GETs have the <code>preserveLastModified</code>
 2603        * option, which takes care of the problem in most use cases where
 2604        * this level of granularity is an issue.
 2605        *
 2606        */
 2607       public static class Granularity extends EnumeratedAttribute {
 2608   
 2609           private static final String[] VALID_GRANULARITIES = {
 2610               "", "MINUTE", "NONE"
 2611           };
 2612   
 2613           /**
 2614            * Get the valid values.
 2615            * @return the list of valid Granularity values
 2616            */
 2617           public String[] getValues() {
 2618               return VALID_GRANULARITIES;
 2619           }
 2620           /**
 2621            * returns the number of milliseconds associated with
 2622            * the attribute, which can vary in some cases depending
 2623            * on the value of the action parameter.
 2624            * @param action SEND_FILES or GET_FILES
 2625            * @return the number of milliseconds associated with
 2626            * the attribute, in the context of the supplied action
 2627            */
 2628           public long getMilliseconds(int action) {
 2629               String granularityU = getValue().toUpperCase(Locale.ENGLISH);
 2630               if ("".equals(granularityU)) {
 2631                   if (action == SEND_FILES) {
 2632                       return GRANULARITY_MINUTE;
 2633                   }
 2634               } else if ("MINUTE".equals(granularityU)) {
 2635                   return GRANULARITY_MINUTE;
 2636               }
 2637               return 0L;
 2638           }
 2639           static final Granularity getDefault() {
 2640               Granularity g = new Granularity();
 2641               g.setValue("");
 2642               return g;
 2643           }
 2644   
 2645       }
 2646       /**
 2647        * one of the valid system type keys recognized by the systemTypeKey
 2648        * attribute.
 2649        */
 2650       public static class FTPSystemType extends EnumeratedAttribute {
 2651   
 2652           private static final String[] VALID_SYSTEM_TYPES = {
 2653               "", "UNIX", "VMS", "WINDOWS", "OS/2", "OS/400",
 2654               "MVS"
 2655           };
 2656   
 2657   
 2658           /**
 2659            * Get the valid values.
 2660            * @return the list of valid system types.
 2661            */
 2662           public String[] getValues() {
 2663               return VALID_SYSTEM_TYPES;
 2664           }
 2665   
 2666           static final FTPSystemType getDefault() {
 2667               FTPSystemType ftpst = new FTPSystemType();
 2668               ftpst.setValue("");
 2669               return ftpst;
 2670           }
 2671       }
 2672       /**
 2673        * Enumerated class for languages.
 2674        */
 2675       public static class LanguageCode extends EnumeratedAttribute {
 2676   
 2677   
 2678           private static final String[] VALID_LANGUAGE_CODES =
 2679               getValidLanguageCodes();
 2680   
 2681           private static String[] getValidLanguageCodes() {
 2682               Collection c = FTPClientConfig.getSupportedLanguageCodes();
 2683               String[] ret = new String[c.size() + 1];
 2684               int i = 0;
 2685               ret[i++] = "";
 2686               for (Iterator it = c.iterator(); it.hasNext(); i++) {
 2687                   ret[i] = (String) it.next();
 2688               }
 2689               return ret;
 2690           }
 2691   
 2692   
 2693           /**
 2694            * Return the value values.
 2695            * @return the list of valid language types.
 2696            */
 2697           public String[] getValues() {
 2698               return VALID_LANGUAGE_CODES;
 2699           }
 2700   
 2701           static final LanguageCode getDefault() {
 2702               LanguageCode lc = new LanguageCode();
 2703               lc.setValue("");
 2704               return lc;
 2705           }
 2706       }
 2707   
 2708   }

Save This Page
Home » apache-ant-1.8.1 » org.apache.tools » ant » taskdefs » optional » net » [javadoc | source]