KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > taskdefs > optional > net > FTP


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 JavaDoc;
21 import java.io.BufferedOutputStream JavaDoc;
22 import java.io.BufferedWriter JavaDoc;
23 import java.io.File JavaDoc;
24 import java.io.FileInputStream JavaDoc;
25 import java.io.FileOutputStream JavaDoc;
26 import java.io.FileWriter JavaDoc;
27 import java.io.IOException JavaDoc;
28 import java.io.InputStream JavaDoc;
29 import java.io.OutputStream JavaDoc;
30 import java.text.SimpleDateFormat JavaDoc;
31 import java.util.Collection JavaDoc;
32 import java.util.Date JavaDoc;
33 import java.util.Enumeration JavaDoc;
34 import java.util.HashMap JavaDoc;
35 import java.util.HashSet JavaDoc;
36 import java.util.Hashtable JavaDoc;
37 import java.util.Iterator JavaDoc;
38 import java.util.Locale JavaDoc;
39 import java.util.Map JavaDoc;
40 import java.util.Set JavaDoc;
41 import java.util.StringTokenizer JavaDoc;
42 import java.util.Vector JavaDoc;
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
60 /**
61  * Basic FTP client. Performs the following actions:
62  * <ul>
63  * <li> <strong>send</strong> - send files to a remote server. This is the
64  * default action.</li>
65  * <li> <strong>get</strong> - retrieve files from a remote server.</li>
66  * <li> <strong>del</strong> - delete files from a remote server.</li>
67  * <li> <strong>list</strong> - create a file listing.</li>
68  * <li> <strong>chmod</strong> - change unix file permissions.</li>
69  * <li> <strong>rmdir</strong> - remove directories, if empty, from a
70  * remote server.</li>
71  * </ul>
72  * <strong>Note:</strong> Some FTP servers - notably the Solaris server - seem
73  * to hold data ports open after a "retr" operation, allowing them to timeout
74  * instead of shutting them down cleanly. This happens in active or passive
75  * mode, and the ports will remain open even after ending the FTP session. FTP
76  * "send" operations seem to close ports immediately. This behavior may cause
77  * problems on some systems when downloading large sets of files.
78  *
79  * @since Ant 1.3
80  */

81 public class FTP
82      extends Task {
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     /** Default port for FTP */
99     public static final int DEFAULT_FTP_PORT = 21;
100
101     private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
102
103     private String JavaDoc remotedir;
104     private String JavaDoc server;
105     private String JavaDoc userid;
106     private String JavaDoc password;
107     private String JavaDoc account;
108     private File JavaDoc listing;
109     private boolean binary = true;
110     private boolean passive = false;
111     private boolean verbose = false;
112     private boolean newerOnly = false;
113     private long timeDiffMillis = 0;
114     private long granularityMillis = 0L;
115     private boolean timeDiffAuto = false;
116     private int action = SEND_FILES;
117     private Vector JavaDoc filesets = new Vector JavaDoc();
118     private Vector JavaDoc dirCache = new Vector JavaDoc();
119     private int transferred = 0;
120     private String JavaDoc remoteFileSep = "/";
121     private int port = DEFAULT_FTP_PORT;
122     private boolean skipFailedTransfers = false;
123     private int skipped = 0;
124     private boolean ignoreNoncriticalErrors = false;
125     private boolean preserveLastModified = false;
126     private String JavaDoc chmod = null;
127     private String JavaDoc umask = null;
128     private FTPSystemType systemTypeKey = FTPSystemType.getDefault();
129     private String JavaDoc defaultDateFormatConfig = null;
130     private String JavaDoc recentDateFormatConfig = null;
131     private LanguageCode serverLanguageCodeConfig = LanguageCode.getDefault();
132     private String JavaDoc serverTimeZoneConfig = null;
133     private String JavaDoc shortMonthNamesConfig = null;
134     private Granularity timestampGranularity = Granularity.getDefault();
135     private boolean isConfigurationSet = false;
136     private int retriesAllowed = 0;
137     private String JavaDoc siteCommand = null;
138     private String JavaDoc initialSiteCommand = null;
139
140     protected static final String JavaDoc[] ACTION_STRS = {
141         "sending",
142         "getting",
143         "deleting",
144         "listing",
145         "making directory",
146         "chmod",
147         "removing",
148         "site"
149         };
150
151     protected static final String JavaDoc[] COMPLETED_ACTION_STRS = {
152         "sent",
153         "retrieved",
154         "deleted",
155         "listed",
156         "created directory",
157         "mode changed",
158         "removed",
159         "site command executed"
160         };
161
162     protected static final String JavaDoc[] ACTION_TARGET_STRS = {
163         "files",
164         "files",
165         "files",
166         "files",
167         "directory",
168         "files",
169         "directories",
170         "site command"
171         };
172
173
174     /**
175      * internal class allowing to read the contents of a remote file system
176      * using the FTP protocol
177      * used in particular for ftp get operations
178      * differences with DirectoryScanner
179      * "" (the root of the fileset) is never included in the included directories
180      * followSymlinks defaults to false
181      */

182     protected class FTPDirectoryScanner extends DirectoryScanner {
183         // CheckStyle:VisibilityModifier OFF - bc
184
protected FTPClient ftp = null;
185         // CheckStyle:VisibilityModifier ON
186

187         private String JavaDoc rootPath = null;
188
189         /**
190          * since ant 1.6
191          * this flag should be set to true on UNIX and can save scanning time
192          */

193         private boolean remoteSystemCaseSensitive = false;
194         private boolean remoteSensitivityChecked = false;
195
196         /**
197          * constructor
198          * @param ftp ftpclient object
199          */

200         public FTPDirectoryScanner(FTPClient ftp) {
201             super();
202             this.ftp = ftp;
203             this.setFollowSymlinks(false);
204         }
205
206
207         /**
208          * scans the remote directory,
209          * storing internally the included files, directories, ...
210          */

211         public void scan() {
212             if (includes == null) {
213                 // No includes supplied, so set it to 'matches all'
214
includes = new String JavaDoc[1];
215                 includes[0] = "**";
216             }
217             if (excludes == null) {
218                 excludes = new String JavaDoc[0];
219             }
220
221             filesIncluded = new Vector JavaDoc();
222             filesNotIncluded = new Vector JavaDoc();
223             filesExcluded = new Vector JavaDoc();
224             dirsIncluded = new Vector JavaDoc();
225             dirsNotIncluded = new Vector JavaDoc();
226             dirsExcluded = new Vector JavaDoc();
227
228             try {
229                 String JavaDoc cwd = ftp.printWorkingDirectory();
230                 // always start from the current ftp working dir
231
forceRemoteSensitivityCheck();
232
233                 checkIncludePatterns();
234                 clearCaches();
235                 ftp.changeWorkingDirectory(cwd);
236             } catch (IOException JavaDoc e) {
237                 throw new BuildException("Unable to scan FTP server: ", e);
238             }
239         }
240
241
242         /**
243          * this routine is actually checking all the include patterns in
244          * order to avoid scanning everything under base dir
245          * @since ant1.6
246          */

247         private void checkIncludePatterns() {
248
249             Hashtable JavaDoc newroots = new Hashtable JavaDoc();
250             // put in the newroots vector the include patterns without
251
// wildcard tokens
252
for (int icounter = 0; icounter < includes.length; icounter++) {
253                 String JavaDoc newpattern =
254                     SelectorUtils.rtrimWildcardTokens(includes[icounter]);
255                 newroots.put(newpattern, includes[icounter]);
256             }
257             if (remotedir == null) {
258                 try {
259                     remotedir = ftp.printWorkingDirectory();
260                 } catch (IOException JavaDoc e) {
261                     throw new BuildException("could not read current ftp directory",
262                         getLocation());
263                 }
264             }
265             AntFTPFile baseFTPFile = new AntFTPRootFile(ftp, remotedir);
266             rootPath = baseFTPFile.getAbsolutePath();
267             // construct it
268
if (newroots.containsKey("")) {
269                 // we are going to scan everything anyway
270
scandir(rootPath, "", true);
271             } else {
272                 // only scan directories that can include matched files or
273
// directories
274
Enumeration JavaDoc enum2 = newroots.keys();
275
276                 while (enum2.hasMoreElements()) {
277                     String JavaDoc currentelement = (String JavaDoc) enum2.nextElement();
278                     String JavaDoc originalpattern = (String JavaDoc) newroots.get(currentelement);
279                     AntFTPFile myfile = new AntFTPFile(baseFTPFile, currentelement);
280                     boolean isOK = true;
281                     boolean traversesSymlinks = false;
282                     String JavaDoc path = null;
283
284                     if (myfile.exists()) {
285                         forceRemoteSensitivityCheck();
286                         if (remoteSensitivityChecked
287                             && remoteSystemCaseSensitive && isFollowSymlinks()) {
288                             // cool case,
289
//we do not need to scan all the subdirs in the relative path
290
path = myfile.getFastRelativePath();
291                         } else {
292                             // may be on a case insensitive file system. We want
293
// the results to show what's really on the disk, so
294
// we need to double check.
295
try {
296                                 path = myfile.getRelativePath();
297                                 traversesSymlinks = myfile.isTraverseSymlinks();
298                             } catch (IOException JavaDoc be) {
299                                 throw new BuildException(be, getLocation());
300                             } catch (BuildException be) {
301                                 isOK = false;
302                             }
303                         }
304                     } else {
305                         isOK = false;
306                     }
307                     if (isOK) {
308                         currentelement = path.replace(remoteFileSep.charAt(0), File.separatorChar);
309                         if (!isFollowSymlinks()
310                             && traversesSymlinks) {
311                             continue;
312                         }
313
314                         if (myfile.isDirectory()) {
315                             if (isIncluded(currentelement)
316                                 && currentelement.length() > 0) {
317                                 accountForIncludedDir(currentelement, myfile, true);
318                             } else {
319                                 if (currentelement.length() > 0) {
320                                     if (currentelement.charAt(currentelement
321                                                               .length() - 1)
322                                         != File.separatorChar) {
323                                         currentelement =
324                                             currentelement + File.separatorChar;
325                                     }
326                                 }
327                                 scandir(myfile.getAbsolutePath(), currentelement, true);
328                             }
329                         } else {
330                             if (isCaseSensitive
331                                 && originalpattern.equals(currentelement)) {
332                                 accountForIncludedFile(currentelement);
333                             } else if (!isCaseSensitive
334                                        && originalpattern
335                                        .equalsIgnoreCase(currentelement)) {
336                                 accountForIncludedFile(currentelement);
337                             }
338                         }
339                     }
340                 }
341             }
342         }
343         /**
344          * scans a particular directory
345          * @param dir directory to scan
346          * @param vpath relative path to the base directory of the remote fileset
347          * always ended with a File.separator
348          * @param fast seems to be always true in practice
349          */

350         protected void scandir(String JavaDoc dir, String JavaDoc vpath, boolean fast) {
351             // avoid double scanning of directories, can only happen in fast mode
352
if (fast && hasBeenScanned(vpath)) {
353                 return;
354             }
355             try {
356                 if (!ftp.changeWorkingDirectory(dir)) {
357                     return;
358                 }
359                 String JavaDoc completePath = null;
360                 if (!vpath.equals("")) {
361                     completePath = rootPath + remoteFileSep
362                         + vpath.replace(File.separatorChar, remoteFileSep.charAt(0));
363                 } else {
364                     completePath = rootPath;
365                 }
366                 FTPFile[] newfiles = listFiles(completePath, false);
367
368                 if (newfiles == null) {
369                     ftp.changeToParentDirectory();
370                     return;
371                 }
372                 for (int i = 0; i < newfiles.length; i++) {
373                     FTPFile file = newfiles[i];
374                     if (!file.getName().equals(".")
375                          && !file.getName().equals("..")) {
376                         if (isFunctioningAsDirectory(ftp, dir, file)) {
377                             String JavaDoc name = vpath + file.getName();
378                             boolean slowScanAllowed = true;
379                             if (!isFollowSymlinks() && file.isSymbolicLink()) {
380                                 dirsExcluded.addElement(name);
381                                 slowScanAllowed = false;
382                             } else if (isIncluded(name)) {
383                                 accountForIncludedDir(name,
384                                     new AntFTPFile(ftp, file, completePath) , fast);
385                             } else {
386                                 dirsNotIncluded.addElement(name);
387                                 if (fast && couldHoldIncluded(name)) {
388                                     scandir(file.getName(),
389                                             name + File.separator, fast);
390                                 }
391                             }
392                             if (!fast && slowScanAllowed) {
393                                 scandir(file.getName(),
394                                         name + File.separator, fast);
395                             }
396                         } else {
397                             String JavaDoc name = vpath + file.getName();
398                             if (!isFollowSymlinks() && file.isSymbolicLink()) {
399                                 filesExcluded.addElement(name);
400                             } else if (isFunctioningAsFile(ftp, dir, file)) {
401                                 accountForIncludedFile(name);
402                             }
403                         }
404                     }
405                 }
406                 ftp.changeToParentDirectory();
407             } catch (IOException JavaDoc e) {
408                 throw new BuildException("Error while communicating with FTP "
409                      + "server: ", e);
410             }
411         }
412         /**
413          * process included file
414          * @param name path of the file relative to the directory of the fileset
415          */

416         private void accountForIncludedFile(String JavaDoc name) {
417             if (!filesIncluded.contains(name)
418                 && !filesExcluded.contains(name)) {
419
420                 if (isIncluded(name)) {
421                     if (!isExcluded(name)) {
422                         filesIncluded.addElement(name);
423                     } else {
424                         filesExcluded.addElement(name);
425                     }
426                 } else {
427                     filesNotIncluded.addElement(name);
428                 }
429             }
430         }
431
432         /**
433          *
434          * @param name path of the directory relative to the directory of
435          * the fileset
436          * @param file directory as file
437          * @param fast
438          */

439         private void accountForIncludedDir(String JavaDoc name, AntFTPFile file, boolean fast) {
440             if (!dirsIncluded.contains(name)
441                 && !dirsExcluded.contains(name)) {
442
443                 if (!isExcluded(name)) {
444                     if (fast) {
445                         if (file.isSymbolicLink()) {
446                             try {
447                                 file.getClient().changeWorkingDirectory(file.curpwd);
448                             } catch (IOException JavaDoc ioe) {
449                                 throw new BuildException("could not change directory to curpwd");
450                             }
451                             scandir(file.getLink(),
452                                 name + File.separator, fast);
453                         } else {
454                             try {
455                                 file.getClient().changeWorkingDirectory(file.curpwd);
456                             } catch (IOException JavaDoc ioe) {
457                                 throw new BuildException("could not change directory to curpwd");
458                             }
459                             scandir(file.getName(),
460                                 name + File.separator, fast);
461                         }
462                     }
463                     dirsIncluded.addElement(name);
464                 } else {
465                     dirsExcluded.addElement(name);
466                     if (fast && couldHoldIncluded(name)) {
467                         try {
468                             file.getClient().changeWorkingDirectory(file.curpwd);
469                         } catch (IOException JavaDoc ioe) {
470                             throw new BuildException("could not change directory to curpwd");
471                         }
472                         scandir(file.getName(),
473                                 name + File.separator, fast);
474                     }
475                 }
476             }
477         }
478         /**
479          * temporary table to speed up the various scanning methods below
480          *
481          * @since Ant 1.6
482          */

483         private Map JavaDoc fileListMap = new HashMap JavaDoc();
484         /**
485          * List of all scanned directories.
486          *
487          * @since Ant 1.6
488          */

489         private Set JavaDoc scannedDirs = new HashSet JavaDoc();
490
491         /**
492          * Has the directory with the given path relative to the base
493          * directory already been scanned?
494          *
495          * <p>Registers the given directory as scanned as a side effect.</p>
496          *
497          * @since Ant 1.6
498          */

499         private boolean hasBeenScanned(String JavaDoc vpath) {
500             return !scannedDirs.add(vpath);
501         }
502
503         /**
504          * Clear internal caches.
505          *
506          * @since Ant 1.6
507          */

508         private void clearCaches() {
509             fileListMap.clear();
510             scannedDirs.clear();
511         }
512         /**
513          * list the files present in one directory.
514          * @param directory full path on the remote side
515          * @param changedir if true change to directory directory before listing
516          * @return array of FTPFile
517          */

518         public FTPFile[] listFiles(String JavaDoc directory, boolean changedir) {
519             //getProject().log("listing files in directory " + directory, Project.MSG_DEBUG);
520
String JavaDoc currentPath = directory;
521             if (changedir) {
522                 try {
523                     boolean result = ftp.changeWorkingDirectory(directory);
524                     if (!result) {
525                         return null;
526                     }
527                     currentPath = ftp.printWorkingDirectory();
528                 } catch (IOException JavaDoc ioe) {
529                     throw new BuildException(ioe, getLocation());
530                 }
531             }
532             if (fileListMap.containsKey(currentPath)) {
533                 getProject().log("filelist map used in listing files", Project.MSG_DEBUG);
534                 return ((FTPFile[]) fileListMap.get(currentPath));
535             }
536             FTPFile[] result = null;
537             try {
538                 result = ftp.listFiles();
539             } catch (IOException JavaDoc ioe) {
540                 throw new BuildException(ioe, getLocation());
541             }
542             fileListMap.put(currentPath, result);
543             if (!remoteSensitivityChecked) {
544                 checkRemoteSensitivity(result, directory);
545             }
546             return result;
547         }
548
549         private void forceRemoteSensitivityCheck() {
550             if (!remoteSensitivityChecked) {
551                 try {
552                     checkRemoteSensitivity(ftp.listFiles(), ftp.printWorkingDirectory());
553                 } catch (IOException JavaDoc ioe) {
554                     throw new BuildException(ioe, getLocation());
555                 }
556             }
557         }
558         /**
559          * cd into one directory and
560          * list the files present in one directory.
561          * @param directory full path on the remote side
562          * @return array of FTPFile
563          */

564         public FTPFile[] listFiles(String JavaDoc directory) {
565             return listFiles(directory, true);
566         }
567         private void checkRemoteSensitivity(FTPFile[] array, String JavaDoc directory) {
568             if (array == null) {
569                 return;
570             }
571             boolean candidateFound = false;
572             String JavaDoc target = null;
573             for (int icounter = 0; icounter < array.length; icounter++) {
574                 if (array[icounter].isDirectory()) {
575                     if (!array[icounter].getName().equals(".")
576                         && !array[icounter].getName().equals("..")) {
577                         candidateFound = true;
578                         target = fiddleName(array[icounter].getName());
579                         getProject().log("will try to cd to "
580                             + target + " where a directory called " + array[icounter].getName()
581                             + " exists", Project.MSG_DEBUG);
582                         for (int pcounter = 0; pcounter < array.length; pcounter++) {
583                             if (array[pcounter].getName().equals(target) && pcounter != icounter) {
584                                 candidateFound = false;
585                             }
586                         }
587                         if (candidateFound) {
588                             break;
589                         }
590                     }
591                 }
592             }
593             if (candidateFound) {
594                 try {
595                     getProject().log("testing case sensitivity, attempting to cd to "
596                         + target, Project.MSG_DEBUG);
597                     remoteSystemCaseSensitive = !ftp.changeWorkingDirectory(target);
598                 } catch (IOException JavaDoc ioe) {
599                     remoteSystemCaseSensitive = true;
600                 } finally {
601                     try {
602                         ftp.changeWorkingDirectory(directory);
603                     } catch (IOException JavaDoc ioe) {
604                         throw new BuildException(ioe, getLocation());
605                     }
606                 }
607                 getProject().log("remote system is case sensitive : " + remoteSystemCaseSensitive,
608                     Project.MSG_VERBOSE);
609                 remoteSensitivityChecked = true;
610             }
611         }
612         private String JavaDoc fiddleName(String JavaDoc origin) {
613             StringBuffer JavaDoc result = new StringBuffer JavaDoc();
614             for (int icounter = 0; icounter < origin.length(); icounter++) {
615                 if (Character.isLowerCase(origin.charAt(icounter))) {
616                     result.append(Character.toUpperCase(origin.charAt(icounter)));
617                 } else if (Character.isUpperCase(origin.charAt(icounter))) {
618                     result.append(Character.toLowerCase(origin.charAt(icounter)));
619                 } else {
620                     result.append(origin.charAt(icounter));
621                 }
622             }
623             return result.toString();
624         }
625         /**
626          * an AntFTPFile is a representation of a remote file
627          * @since Ant 1.6
628          */

629         protected class AntFTPFile {
630             /**
631              * ftp client
632              */

633             private FTPClient client;
634             /**
635              * parent directory of the file
636              */

637             private String JavaDoc curpwd;
638             /**
639              * the file itself
640              */

641             private FTPFile ftpFile;
642             /**
643              *
644              */

645             private AntFTPFile parent = null;
646             private boolean relativePathCalculated = false;
647             private boolean traversesSymlinks = false;
648             private String JavaDoc relativePath = "";
649             /**
650              * constructor
651              * @param client ftp client variable
652              * @param ftpFile the file
653              * @param curpwd absolute remote path where the file is found
654              */

655             public AntFTPFile(FTPClient client, FTPFile ftpFile, String JavaDoc curpwd) {
656                 this.client = client;
657                 this.ftpFile = ftpFile;
658                 this.curpwd = curpwd;
659             }
660             /**
661              * other constructor
662              * @param parent the parent file
663              * @param path a relative path to the parent file
664              */

665             public AntFTPFile(AntFTPFile parent, String JavaDoc path) {
666                 this.parent = parent;
667                 this.client = parent.client;
668                 Vector JavaDoc pathElements = SelectorUtils.tokenizePath(path);
669                 try {
670                     boolean result = this.client.changeWorkingDirectory(parent.getAbsolutePath());
671                     //this should not happen, except if parent has been deleted by another process
672
if (!result) {
673                         return;
674                     }
675                     this.curpwd = parent.getAbsolutePath();
676                 } catch (IOException JavaDoc ioe) {
677                     throw new BuildException("could not change working dir to "
678                     + parent.curpwd);
679                 }
680                 for (int fcount = 0; fcount < pathElements.size() - 1; fcount++) {
681                     String JavaDoc currentPathElement = (String JavaDoc) pathElements.elementAt(fcount);
682                     try {
683                         boolean result = this.client.changeWorkingDirectory(currentPathElement);
684                         if (!result && !isCaseSensitive()
685                             && (remoteSystemCaseSensitive || !remoteSensitivityChecked)) {
686                            currentPathElement = findPathElementCaseUnsensitive(this.curpwd,
687                                currentPathElement);
688                             if (currentPathElement == null) {
689                                 return;
690                             }
691                         } else if (!result) {
692                             return;
693                         }
694                         this.curpwd = this.curpwd + remoteFileSep
695                             + currentPathElement;
696                     } catch (IOException JavaDoc ioe) {
697                         throw new BuildException("could not change working dir to "
698                         + (String JavaDoc) pathElements.elementAt(fcount)
699                             + " from " + this.curpwd);
700                     }
701
702                 }
703                 String JavaDoc lastpathelement = (String JavaDoc) pathElements.elementAt(pathElements.size() - 1);
704                 FTPFile [] theFiles = listFiles(this.curpwd);
705                 this.ftpFile = getFile(theFiles, lastpathelement);
706             }
707             /**
708              * find a file in a directory in case unsensitive way
709              * @param parentPath where we are
710              * @param soughtPathElement what is being sought
711              * @return the first file found or null if not found
712              */

713             private String JavaDoc findPathElementCaseUnsensitive(String JavaDoc parentPath,
714                                String JavaDoc soughtPathElement) {
715                 // we are already in the right path, so the second parameter
716
// is false
717
FTPFile[] theFiles = listFiles(parentPath, false);
718                 if (theFiles == null) {
719                     return null;
720                 }
721                 for (int icounter = 0; icounter < theFiles.length; icounter++) {
722                     if (theFiles[icounter].getName().equalsIgnoreCase(soughtPathElement)) {
723                         return theFiles[icounter].getName();
724                     }
725                 }
726                 return null;
727             }
728             /**
729              * find out if the file exists
730              * @return true if the file exists
731              */

732             public boolean exists() {
733                 return (ftpFile != null);
734             }
735             /**
736              * if the file is a symbolic link, find out to what it is pointing
737              * @return the target of the symbolic link
738              */

739             public String JavaDoc getLink() {
740                 return ftpFile.getLink();
741             }
742             /**
743              * get the name of the file
744              * @return the name of the file
745              */

746             public String JavaDoc getName() {
747                 return ftpFile.getName();
748             }
749             /**
750              * find out the absolute path of the file
751              * @return absolute path as string
752              */

753             public String JavaDoc getAbsolutePath() {
754                 return curpwd + remoteFileSep + ftpFile.getName();
755             }
756             /**
757              * find out the relative path assuming that the path used to construct
758              * this AntFTPFile was spelled properly with regards to case.
759              * This is OK on a case sensitive system such as UNIX
760              * @return relative path
761              */

762             public String JavaDoc getFastRelativePath() {
763                 String JavaDoc absPath = getAbsolutePath();
764                 if (absPath.indexOf(rootPath + remoteFileSep) == 0) {
765                     return absPath.substring(rootPath.length() + remoteFileSep.length());
766                 }
767                 return null;
768             }
769             /**
770              * find out the relative path to the rootPath of the enclosing scanner.
771              * this relative path is spelled exactly like on disk,
772              * for instance if the AntFTPFile has been instantiated as ALPHA,
773              * but the file is really called alpha, this method will return alpha.
774              * If a symbolic link is encountered, it is followed, but the name of the link
775              * rather than the name of the target is returned.
776              * (ie does not behave like File.getCanonicalPath())
777              * @return relative path, separated by remoteFileSep
778              * @throws IOException if a change directory fails, ...
779              * @throws BuildException if one of the components of the relative path cannot
780              * be found.
781              */

782             public String JavaDoc getRelativePath() throws IOException JavaDoc, BuildException {
783                 if (!relativePathCalculated) {
784                     if (parent != null) {
785                         traversesSymlinks = parent.isTraverseSymlinks();
786                         relativePath = getRelativePath(parent.getAbsolutePath(),
787                             parent.getRelativePath());
788                     } else {
789                         relativePath = getRelativePath(rootPath, "");
790                         relativePathCalculated = true;
791                     }
792                 }
793                 return relativePath;
794             }
795             /**
796              * get thge relative path of this file
797              * @param currentPath base path
798              * @param currentRelativePath relative path of the base path with regards to remote dir
799              * @return relative path
800              */

801             private String JavaDoc getRelativePath(String JavaDoc currentPath, String JavaDoc currentRelativePath) {
802                 Vector JavaDoc pathElements = SelectorUtils.tokenizePath(getAbsolutePath(), remoteFileSep);
803                 Vector JavaDoc pathElements2 = SelectorUtils.tokenizePath(currentPath, remoteFileSep);
804                 String JavaDoc relPath = currentRelativePath;
805                 for (int pcount = pathElements2.size(); pcount < pathElements.size(); pcount++) {
806                     String JavaDoc currentElement = (String JavaDoc) pathElements.elementAt(pcount);
807                     FTPFile[] theFiles = listFiles(currentPath);
808                     FTPFile theFile = null;
809                     if (theFiles != null) {
810                         theFile = getFile(theFiles, currentElement);
811                     }
812                     if (!relPath.equals("")) {
813                         relPath = relPath + remoteFileSep;
814                     }
815                     if (theFile == null) {
816                         // hit a hidden file assume not a symlink
817
relPath = relPath + currentElement;
818                         currentPath = currentPath + remoteFileSep + currentElement;
819                         log("Hidden file " + relPath
820                             + " assumed to not be a symlink.",
821                             Project.MSG_VERBOSE);
822                     } else {
823                         traversesSymlinks = traversesSymlinks || theFile.isSymbolicLink();
824                         relPath = relPath + theFile.getName();
825                         currentPath = currentPath + remoteFileSep + theFile.getName();
826                     }
827                 }
828                 return relPath;
829             }
830             /**
831              * find a file matching a string in an array of FTPFile.
832              * This method will find "alpha" when requested for "ALPHA"
833              * if and only if the caseSensitive attribute is set to false.
834              * When caseSensitive is set to true, only the exact match is returned.
835              * @param theFiles array of files
836              * @param lastpathelement the file name being sought
837              * @return null if the file cannot be found, otherwise return the matching file.
838              */

839             public FTPFile getFile(FTPFile[] theFiles, String JavaDoc lastpathelement) {
840                 if (theFiles == null) {
841                     return null;
842                 }
843                 for (int fcount = 0; fcount < theFiles.length; fcount++) {
844                      if (theFiles[fcount].getName().equals(lastpathelement)) {
845                          return theFiles[fcount];
846                      } else if (!isCaseSensitive()
847                          && theFiles[fcount].getName().equalsIgnoreCase(lastpathelement)) {
848                          return theFiles[fcount];
849                      }
850                 }
851                 return null;
852             }
853             /**
854              * tell if a file is a directory.
855              * note that it will return false for symbolic links pointing to directories.
856              * @return <code>true</code> for directories
857              */

858             public boolean isDirectory() {
859                 return ftpFile.isDirectory();
860             }
861             /**
862              * tell if a file is a symbolic link
863              * @return <code>true</code> for symbolic links
864              */

865             public boolean isSymbolicLink() {
866                 return ftpFile.isSymbolicLink();
867             }
868             /**
869              * return the attached FTP client object.
870              * Warning : this instance is really shared with the enclosing class.
871              * @return FTP client
872              */

873             protected FTPClient getClient() {
874                 return client;
875             }
876
877             /**
878              * sets the current path of an AntFTPFile
879              * @param curpwd the current path one wants to set
880              */

881             protected void setCurpwd(String JavaDoc curpwd) {
882                 this.curpwd = curpwd;
883             }
884             /**
885              * returns the path of the directory containing the AntFTPFile.
886              * of the full path of the file itself in case of AntFTPRootFile
887              * @return parent directory of the AntFTPFile
888              */

889             public String JavaDoc getCurpwd() {
890                 return curpwd;
891             }
892             /**
893              * find out if a symbolic link is encountered in the relative path of this file
894              * from rootPath.
895              * @return <code>true</code> if a symbolic link is encountered in the relative path.
896              * @throws IOException if one of the change directory or directory listing operations
897              * fails
898              * @throws BuildException if a path component in the relative path cannot be found.
899              */

900             public boolean isTraverseSymlinks() throws IOException JavaDoc, BuildException {
901                 if (!relativePathCalculated) {
902                     // getRelativePath also finds about symlinks
903
getRelativePath();
904                 }
905                 return traversesSymlinks;
906             }
907
908             /**
909              * Get a string rep of this object.
910              * @return a string containing the pwd and the file.
911              */

912             public String JavaDoc toString() {
913                 return "AntFtpFile: " + curpwd + "%" + ftpFile;
914             }
915         }
916         /**
917          * special class to represent the remote directory itself
918          * @since Ant 1.6
919          */

920         protected class AntFTPRootFile extends AntFTPFile {
921              private String JavaDoc remotedir;
922             /**
923              * constructor
924              * @param aclient FTP client
925              * @param remotedir remote directory
926              */

927              public AntFTPRootFile(FTPClient aclient, String JavaDoc remotedir) {
928                  super(aclient, null, remotedir);
929                  this.remotedir = remotedir;
930                  try {
931                      this.getClient().changeWorkingDirectory(this.remotedir);
932                      this.setCurpwd(this.getClient().printWorkingDirectory());
933                  } catch (IOException JavaDoc ioe) {
934                      throw new BuildException(ioe, getLocation());
935                  }
936              }
937             /**
938              * find the absolute path
939              * @return absolute path
940              */

941             public String JavaDoc getAbsolutePath() {
942                 return this.getCurpwd();
943             }
944             /**
945              * find out the relative path to root
946              * @return empty string
947              * @throws BuildException actually never
948              * @throws IOException actually never
949              */

950             public String JavaDoc getRelativePath() throws BuildException, IOException JavaDoc {
951                  return "";
952             }
953         }
954     }
955     /**
956      * check FTPFiles to check whether they function as directories too
957      * the FTPFile API seem to make directory and symbolic links incompatible
958      * we want to find out if we can cd to a symbolic link
959      * @param dir the parent directory of the file to test
960      * @param file the file to test
961      * @return true if it is possible to cd to this directory
962      * @since ant 1.6
963      */

964     private boolean isFunctioningAsDirectory(FTPClient ftp, String JavaDoc dir, FTPFile file) {
965         boolean result = false;
966         String JavaDoc currentWorkingDir = null;
967         if (file.isDirectory()) {
968             return true;
969         } else if (file.isFile()) {
970             return false;
971         }
972         try {
973             currentWorkingDir = ftp.printWorkingDirectory();
974         } catch (IOException JavaDoc ioe) {
975             getProject().log("could not find current working directory " + dir
976                 + " while checking a symlink",
977                 Project.MSG_DEBUG);
978         }
979         if (currentWorkingDir != null) {
980             try {
981                 result = ftp.changeWorkingDirectory(file.getLink());
982             } catch (IOException JavaDoc ioe) {
983                 getProject().log("could not cd to " + file.getLink() + " while checking a symlink",
984                     Project.MSG_DEBUG);
985             }
986             if (result) {
987                 boolean comeback = false;
988                 try {
989                     comeback = ftp.changeWorkingDirectory(currentWorkingDir);
990                 } catch (IOException JavaDoc ioe) {
991                     getProject().log("could not cd back to " + dir + " while checking a symlink",
992                         Project.MSG_ERR);
993                 } finally {
994                     if (!comeback) {
995                         throw new BuildException("could not cd back to " + dir
996                             + " while checking a symlink");
997                     }
998                 }
999             }
1000        }
1001        return result;
1002    }
1003    /**
1004     * check FTPFiles to check whether they function as directories too
1005     * the FTPFile API seem to make directory and symbolic links incompatible
1006     * we want to find out if we can cd to a symbolic link
1007     * @param dir the parent directory of the file to test
1008     * @param file the file to test
1009     * @return true if it is possible to cd to this directory
1010     * @since ant 1.6
1011     */

1012    private boolean isFunctioningAsFile(FTPClient ftp, String JavaDoc dir, FTPFile file) {
1013        if (file.isDirectory()) {
1014            return false;
1015        } else if (file.isFile()) {
1016            return true;
1017        }
1018        return !isFunctioningAsDirectory(ftp, dir, file);
1019    }
1020    /**
1021     * Sets the remote directory where files will be placed. This may be a
1022     * relative or absolute path, and must be in the path syntax expected by
1023     * the remote server. No correction of path syntax will be performed.
1024     *
1025     * @param dir the remote directory name.
1026     */

1027    public void setRemotedir(String JavaDoc dir) {
1028        this.remotedir = dir;
1029    }
1030
1031
1032    /**
1033     * Sets the FTP server to send files to.
1034     *
1035     * @param server the remote server name.
1036     */

1037    public void setServer(String JavaDoc server) {
1038        this.server = server;
1039    }
1040
1041
1042    /**
1043     * Sets the FTP port used by the remote server.
1044     *
1045     * @param port the port on which the remote server is listening.
1046     */

1047    public void setPort(int port) {
1048        this.port = port;
1049    }
1050
1051
1052    /**
1053     * Sets the login user id to use on the specified server.
1054     *
1055     * @param userid remote system userid.
1056     */

1057    public void setUserid(String JavaDoc userid) {
1058        this.userid = userid;
1059    }
1060
1061
1062    /**
1063     * Sets the login password for the given user id.
1064     *
1065     * @param password the password on the remote system.
1066     */

1067    public void setPassword(String JavaDoc password) {
1068        this.password = password;
1069    }
1070
1071    /**
1072     * Sets the login account to use on the specified server.
1073     *
1074     * @param pAccount the account name on remote system
1075     * @since Ant 1.7
1076     */

1077    public void setAccount(String JavaDoc pAccount) {
1078        this.account = pAccount;
1079    }
1080
1081
1082    /**
1083     * If true, uses binary mode, otherwise text mode (default is binary).
1084     *
1085     * @param binary if true use binary mode in transfers.
1086     */

1087    public void setBinary(boolean binary) {
1088        this.binary = binary;
1089    }
1090
1091
1092    /**
1093     * Specifies whether to use passive mode. Set to true if you are behind a
1094     * firewall and cannot connect without it. Passive mode is disabled by
1095     * default.
1096     *
1097     * @param passive true is passive mode should be used.
1098     */

1099    public void setPassive(boolean passive) {
1100        this.passive = passive;
1101    }
1102
1103
1104    /**
1105     * Set to true to receive notification about each file as it is
1106     * transferred.
1107     *
1108     * @param verbose true if verbose notifications are required.
1109     */

1110    public void setVerbose(boolean verbose) {
1111        this.verbose = verbose;
1112    }
1113
1114
1115    /**
1116     * A synonym for <tt>depends</tt>. Set to true to transmit only new
1117     * or changed files.
1118     *
1119     * See the related attributes timediffmillis and timediffauto.
1120     *
1121     * @param newer if true only transfer newer files.
1122     */

1123    public void setNewer(boolean newer) {
1124        this.newerOnly = newer;
1125    }
1126
1127    /**
1128     * number of milliseconds to add to the time on the remote machine
1129     * to get the time on the local machine.
1130     *
1131     * use in conjunction with <code>newer</code>
1132     *
1133     * @param timeDiffMillis number of milliseconds
1134     *
1135     * @since ant 1.6
1136     */

1137    public void setTimeDiffMillis(long timeDiffMillis) {
1138        this.timeDiffMillis = timeDiffMillis;
1139    }
1140
1141    /**
1142     * &quot;true&quot; to find out automatically the time difference
1143     * between local and remote machine.
1144     *
1145     * This requires right to create
1146     * and delete a temporary file in the remote directory.
1147     *
1148     * @param timeDiffAuto true = find automatically the time diff
1149     *
1150     * @since ant 1.6
1151     */

1152    public void setTimeDiffAuto(boolean timeDiffAuto) {
1153        this.timeDiffAuto = timeDiffAuto;
1154    }
1155
1156    /**
1157     * Set to true to preserve modification times for "gotten" files.
1158     *
1159     * @param preserveLastModified if true preserver modification times.
1160     */

1161    public void setPreserveLastModified(boolean preserveLastModified) {
1162        this.preserveLastModified = preserveLastModified;
1163    }
1164
1165
1166    /**
1167     * Set to true to transmit only files that are new or changed from their
1168     * remote counterparts. The default is to transmit all files.
1169     *
1170     * @param depends if true only transfer newer files.
1171     */

1172    public void setDepends(boolean depends) {
1173        this.newerOnly = depends;
1174    }
1175
1176
1177    /**
1178     * Sets the remote file separator character. This normally defaults to the
1179     * Unix standard forward slash, but can be manually overridden using this
1180     * call if the remote server requires some other separator. Only the first
1181     * character of the string is used.
1182     *
1183     * @param separator the file separator on the remote system.
1184     */

1185    public void setSeparator(String JavaDoc separator) {
1186        remoteFileSep = separator;
1187    }
1188
1189
1190    /**
1191     * Sets the file permission mode (Unix only) for files sent to the
1192     * server.
1193     *
1194     * @param theMode unix style file mode for the files sent to the remote
1195     * system.
1196     */

1197    public void setChmod(String JavaDoc theMode) {
1198        this.chmod = theMode;
1199    }
1200
1201
1202    /**
1203     * Sets the default mask for file creation on a unix server.
1204     *
1205     * @param theUmask unix style umask for files created on the remote server.
1206     */

1207    public void setUmask(String JavaDoc theUmask) {
1208        this.umask = theUmask;
1209    }
1210
1211
1212    /**
1213     * A set of files to upload or download
1214     *
1215     * @param set the set of files to be added to the list of files to be
1216     * transferred.
1217     */

1218    public void addFileset(FileSet set) {
1219        filesets.addElement(set);
1220    }
1221
1222
1223    /**
1224     * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
1225     * "mkdir", "chmod", "list", and "site".
1226     *
1227     * @deprecated since 1.5.x.
1228     * setAction(String) is deprecated and is replaced with
1229     * setAction(FTP.Action) to make Ant's Introspection mechanism do the
1230     * work and also to encapsulate operations on the type in its own
1231     * class.
1232     * @ant.attribute ignore="true"
1233     *
1234     * @param action the FTP action to be performed.
1235     *
1236     * @throws BuildException if the action is not a valid action.
1237     */

1238    public void setAction(String JavaDoc action) throws BuildException {
1239        log("DEPRECATED - The setAction(String) method has been deprecated."
1240             + " Use setAction(FTP.Action) instead.");
1241
1242        Action a = new Action();
1243
1244        a.setValue(action);
1245        this.action = a.getAction();
1246    }
1247
1248
1249    /**
1250     * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
1251     * "mkdir", "chmod", "list", and "site".
1252     *
1253     * @param action the FTP action to be performed.
1254     *
1255     * @throws BuildException if the action is not a valid action.
1256     */

1257    public void setAction(Action action) throws BuildException {
1258        this.action = action.getAction();
1259    }
1260
1261
1262    /**
1263     * The output file for the "list" action. This attribute is ignored for
1264     * any other actions.
1265     *
1266     * @param listing file in which to store the listing.
1267     */

1268    public void setListing(File JavaDoc listing) {
1269        this.listing = listing;
1270    }
1271
1272
1273    /**
1274     * If true, enables unsuccessful file put, delete and get
1275     * operations to be skipped with a warning and the remainder
1276     * of the files still transferred.
1277     *
1278     * @param skipFailedTransfers true if failures in transfers are ignored.
1279     */

1280    public void setSkipFailedTransfers(boolean skipFailedTransfers) {
1281        this.skipFailedTransfers = skipFailedTransfers;
1282    }
1283
1284
1285    /**
1286     * set the flag to skip errors on directory creation.
1287     * (and maybe later other server specific errors)
1288     *
1289     * @param ignoreNoncriticalErrors true if non-critical errors should not
1290     * cause a failure.
1291     */

1292    public void setIgnoreNoncriticalErrors(boolean ignoreNoncriticalErrors) {
1293        this.ignoreNoncriticalErrors = ignoreNoncriticalErrors;
1294    }
1295
1296    private void configurationHasBeenSet() {
1297        this.isConfigurationSet = true;
1298    }
1299
1300    /**
1301     * Sets the systemTypeKey attribute.
1302     * Method for setting <code>FTPClientConfig</code> remote system key.
1303     *
1304     * @param systemKey the key to be set - BUT if blank
1305     * the default value of null (which signifies "autodetect") will be kept.
1306     * @see org.apache.commons.net.ftp.FTPClientConfig
1307     */

1308    public void setSystemTypeKey(FTPSystemType systemKey) {
1309        if (systemKey != null && !systemKey.getValue().equals("")) {
1310            this.systemTypeKey = systemKey;
1311            configurationHasBeenSet();
1312        }
1313    }
1314
1315    /**
1316     * Sets the defaultDateFormatConfig attribute.
1317     * @param defaultDateFormat configuration to be set, unless it is
1318     * null or empty string, in which case ignored.
1319     * @see org.apache.commons.net.ftp.FTPClientConfig
1320     */

1321    public void setDefaultDateFormatConfig(String JavaDoc defaultDateFormat) {
1322        if (defaultDateFormat != null && !defaultDateFormat.equals("")) {
1323            this.defaultDateFormatConfig = defaultDateFormat;
1324            configurationHasBeenSet();
1325        }
1326    }
1327
1328    /**
1329     * Sets the recentDateFormatConfig attribute.
1330     * @param recentDateFormat configuration to be set, unless it is
1331     * null or empty string, in which case ignored.
1332     * @see org.apache.commons.net.ftp.FTPClientConfig
1333     */

1334    public void setRecentDateFormatConfig(String JavaDoc recentDateFormat) {
1335        if (recentDateFormat != null && !recentDateFormat.equals("")) {
1336            this.recentDateFormatConfig = recentDateFormat;
1337            configurationHasBeenSet();
1338        }
1339    }
1340
1341    /**
1342     * Sets the serverLanguageCode attribute.
1343     * @param serverLanguageCode configuration to be set, unless it is
1344     * null or empty string, in which case ignored.
1345     * @see org.apache.commons.net.ftp.FTPClientConfig
1346     */

1347    public void setServerLanguageCodeConfig(LanguageCode serverLanguageCode) {
1348        if (serverLanguageCode != null && !serverLanguageCode.equals("")) {
1349            this.serverLanguageCodeConfig = serverLanguageCode;
1350            configurationHasBeenSet();
1351        }
1352    }
1353
1354    /**
1355     * Sets the serverTimeZoneConfig attribute.
1356     * @param serverTimeZoneId configuration to be set, unless it is
1357     * null or empty string, in which case ignored.
1358     * @see org.apache.commons.net.ftp.FTPClientConfig
1359     */

1360    public void setServerTimeZoneConfig(String JavaDoc serverTimeZoneId) {
1361        if (serverTimeZoneId != null && !serverTimeZoneId.equals("")) {
1362            this.serverTimeZoneConfig = serverTimeZoneId;
1363            configurationHasBeenSet();
1364        }
1365    }
1366
1367    /**
1368     * Sets the shortMonthNamesConfig attribute
1369     *
1370     * @param shortMonthNames configuration to be set, unless it is
1371     * null or empty string, in which case ignored.
1372     * @see org.apache.commons.net.ftp.FTPClientConfig
1373     */

1374    public void setShortMonthNamesConfig(String JavaDoc shortMonthNames) {
1375        if (shortMonthNames != null && !shortMonthNames.equals("")) {
1376            this.shortMonthNamesConfig = shortMonthNames;
1377            configurationHasBeenSet();
1378        }
1379    }
1380
1381
1382
1383    /**
1384     * Defines how many times to retry executing FTP command before giving up.
1385     * Default is 0 - try once and if failure then give up.
1386     *
1387     * @param retriesAllowed number of retries to allow. -1 means
1388     * keep trying forever. "forever" may also be specified as a
1389     * synonym for -1.
1390     */

1391    public void setRetriesAllowed(String JavaDoc retriesAllowed) {
1392        if ("FOREVER".equalsIgnoreCase(retriesAllowed)) {
1393            this.retriesAllowed = Retryable.RETRY_FOREVER;
1394        } else {
1395            try {
1396                int retries = Integer.parseInt(retriesAllowed);
1397                if (retries < Retryable.RETRY_FOREVER) {
1398                    throw new BuildException(
1399                            "Invalid value for retriesAllowed attribute: "
1400                            + retriesAllowed);
1401
1402                }
1403                this.retriesAllowed = retries;
1404            } catch (NumberFormatException JavaDoc px) {
1405                throw new BuildException(
1406                        "Invalid value for retriesAllowed attribute: "
1407                        + retriesAllowed);
1408
1409            }
1410
1411        }
1412    }
1413    /**
1414     * @return Returns the systemTypeKey.
1415     */

1416    String JavaDoc getSystemTypeKey() {
1417        return systemTypeKey.getValue();
1418    }
1419    /**
1420     * @return Returns the defaultDateFormatConfig.
1421     */

1422    String JavaDoc getDefaultDateFormatConfig() {
1423        return defaultDateFormatConfig;
1424    }
1425    /**
1426     * @return Returns the recentDateFormatConfig.
1427     */

1428    String JavaDoc getRecentDateFormatConfig() {
1429        return recentDateFormatConfig;
1430    }
1431    /**
1432     * @return Returns the serverLanguageCodeConfig.
1433     */

1434    String JavaDoc getServerLanguageCodeConfig() {
1435        return serverLanguageCodeConfig.getValue();
1436    }
1437    /**
1438     * @return Returns the serverTimeZoneConfig.
1439     */

1440    String JavaDoc getServerTimeZoneConfig() {
1441        return serverTimeZoneConfig;
1442    }
1443    /**
1444     * @return Returns the shortMonthNamesConfig.
1445     */

1446    String JavaDoc getShortMonthNamesConfig() {
1447        return shortMonthNamesConfig;
1448    }
1449    /**
1450     * @return Returns the timestampGranularity.
1451     */

1452    Granularity getTimestampGranularity() {
1453        return timestampGranularity;
1454    }
1455    /**
1456     * Sets the timestampGranularity attribute
1457     * @param timestampGranularity The timestampGranularity to set.
1458     */

1459    public void setTimestampGranularity(Granularity timestampGranularity) {
1460        if (null == timestampGranularity || "".equals(timestampGranularity)) {
1461            return;
1462        }
1463        this.timestampGranularity = timestampGranularity;
1464     }
1465    /**
1466     * Sets the siteCommand attribute. This attribute
1467     * names the command that will be executed if the action
1468     * is "site".
1469     * @param siteCommand The siteCommand to set.
1470     */

1471    public void setSiteCommand(String JavaDoc siteCommand) {
1472        this.siteCommand = siteCommand;
1473    }
1474    /**
1475     * Sets the initialSiteCommand attribute. This attribute
1476     * names a site command that will be executed immediately
1477     * after connection.
1478     * @param initialCommand The initialSiteCommand to set.
1479     */

1480    public void setInitialSiteCommand(String JavaDoc initialCommand) {
1481        this.initialSiteCommand = initialCommand;
1482    }
1483    /**
1484     * Checks to see that all required parameters are set.
1485     *
1486     * @throws BuildException if the configuration is not valid.
1487     */

1488    protected void checkAttributes() throws BuildException {
1489        if (server == null) {
1490            throw new BuildException("server attribute must be set!");
1491        }
1492        if (userid == null) {
1493            throw new BuildException("userid attribute must be set!");
1494        }
1495        if (password == null) {
1496            throw new BuildException("password attribute must be set!");
1497        }
1498
1499        if ((action == LIST_FILES) && (listing == null)) {
1500            throw new BuildException("listing attribute must be set for list "
1501                 + "action!");
1502        }
1503
1504        if (action == MK_DIR && remotedir == null) {
1505            throw new BuildException("remotedir attribute must be set for "
1506                 + "mkdir action!");
1507        }
1508
1509        if (action == CHMOD && chmod == null) {
1510            throw new BuildException("chmod attribute must be set for chmod "
1511                 + "action!");
1512        }
1513        if (action == SITE_CMD && siteCommand == null) {
1514            throw new BuildException("sitecommand attribute must be set for site "
1515                 + "action!");
1516        }
1517
1518
1519        if (this.isConfigurationSet) {
1520            try {
1521                Class.forName("org.apache.commons.net.ftp.FTPClientConfig");
1522            } catch (ClassNotFoundException JavaDoc e) {
1523                throw new BuildException(
1524                 "commons-net.jar >= 1.4.0 is required for at least one"
1525                 + " of the attributes specified.");
1526            }
1527        }
1528    }
1529
1530    /**
1531     * Executable a retryable object.
1532     * @param h the retry hander.
1533     * @param r the object that should be retried until it succeeds
1534     * or the number of retrys is reached.
1535     * @param descr a description of the command that is being run.
1536     * @throws IOException if there is a problem.
1537     */

1538    protected void executeRetryable(RetryHandler h, Retryable r, String JavaDoc descr)
1539        throws IOException JavaDoc {
1540        h.execute(r, descr);
1541    }
1542
1543
1544    /**
1545     * For each file in the fileset, do the appropriate action: send, get,
1546     * delete, or list.
1547     *
1548     * @param ftp the FTPClient instance used to perform FTP actions
1549     * @param fs the fileset on which the actions are performed.
1550     *
1551     * @return the number of files to be transferred.
1552     *
1553     * @throws IOException if there is a problem reading a file
1554     * @throws BuildException if there is a problem in the configuration.
1555     */

1556    protected int transferFiles(final FTPClient ftp, FileSet fs)
1557         throws IOException JavaDoc, BuildException {
1558        DirectoryScanner ds;
1559        if (action == SEND_FILES) {
1560            ds = fs.getDirectoryScanner(getProject());
1561        } else {
1562            // warn that selectors are not supported
1563
if (fs.getSelectors(getProject()).length != 0) {
1564                getProject().log("selectors are not supported in remote filesets",
1565                    Project.MSG_WARN);
1566            }
1567            ds = new FTPDirectoryScanner(ftp);
1568            fs.setupDirectoryScanner(ds, getProject());
1569            ds.setFollowSymlinks(fs.isFollowSymlinks());
1570            ds.scan();
1571        }
1572
1573        String JavaDoc[] dsfiles = null;
1574        if (action == RM_DIR) {
1575            dsfiles = ds.getIncludedDirectories();
1576        } else {
1577            dsfiles = ds.getIncludedFiles();
1578        }
1579        String JavaDoc dir = null;
1580
1581        if ((ds.getBasedir() == null)
1582             && ((action == SEND_FILES) || (action == GET_FILES))) {
1583            throw new BuildException("the dir attribute must be set for send "
1584                 + "and get actions");
1585        } else {
1586            if ((action == SEND_FILES) || (action == GET_FILES)) {
1587                dir = ds.getBasedir().getAbsolutePath();
1588            }
1589        }
1590
1591        // If we are doing a listing, we need the output stream created now.
1592
BufferedWriter JavaDoc bw = null;
1593
1594        try {
1595            if (action == LIST_FILES) {
1596                File JavaDoc pd = listing.getParentFile();
1597
1598                if (!pd.exists()) {
1599                    pd.mkdirs();
1600                }
1601                bw = new BufferedWriter JavaDoc(new FileWriter JavaDoc(listing));
1602            }
1603            RetryHandler h = new RetryHandler(this.retriesAllowed, this);
1604            if (action == RM_DIR) {
1605                // to remove directories, start by the end of the list
1606
// the trunk does not let itself be removed before the leaves
1607
for (int i = dsfiles.length - 1; i >= 0; i--) {
1608                    final String JavaDoc dsfile = dsfiles[i];
1609                    executeRetryable(h, new Retryable() {
1610                        public void execute() throws IOException JavaDoc {
1611                            rmDir(ftp, dsfile);
1612                        }
1613                    }, dsfile);
1614                }
1615            } else {
1616                final BufferedWriter JavaDoc fbw = bw;
1617                final String JavaDoc fdir = dir;
1618                if (this.newerOnly) {
1619                    this.granularityMillis =
1620                        this.timestampGranularity.getMilliseconds(action);
1621                }
1622                for (int i = 0; i < dsfiles.length; i++) {
1623                    final String JavaDoc dsfile = dsfiles[i];
1624                    executeRetryable(h, new Retryable() {
1625                        public void execute() throws IOException JavaDoc {
1626                            switch (action) {
1627                                case SEND_FILES:
1628                                    sendFile(ftp, fdir, dsfile);
1629                                    break;
1630                                case GET_FILES:
1631                                    getFile(ftp, fdir, dsfile);
1632                                    break;
1633                                case DEL_FILES:
1634                                    delFile(ftp, dsfile);
1635                                    break;
1636                                case LIST_FILES:
1637                                    listFile(ftp, fbw, dsfile);
1638                                    break;
1639                                case CHMOD:
1640                                    doSiteCommand(ftp, "chmod " + chmod
1641                                                  + " " + resolveFile(dsfile));
1642                                    transferred++;
1643                                    break;
1644                                default:
1645                                    throw new BuildException("unknown ftp action " + action);
1646                            }
1647                        }
1648                    }, dsfile);
1649                }
1650            }
1651        } finally {
1652            if (bw != null) {
1653                bw.close();
1654            }
1655        }
1656
1657        return dsfiles.length;
1658    }
1659
1660
1661    /**
1662     * Sends all files specified by the configured filesets to the remote
1663     * server.
1664     *
1665     * @param ftp the FTPClient instance used to perform FTP actions
1666     *
1667     * @throws IOException if there is a problem reading a file
1668     * @throws BuildException if there is a problem in the configuration.
1669     */

1670    protected void transferFiles(FTPClient ftp)
1671         throws IOException JavaDoc, BuildException {
1672        transferred = 0;
1673        skipped = 0;
1674
1675        if (filesets.size() == 0) {
1676            throw new BuildException("at least one fileset must be specified.");
1677        } else {
1678            // get files from filesets
1679
for (int i = 0; i < filesets.size(); i++) {
1680                FileSet fs = (FileSet) filesets.elementAt(i);
1681
1682                if (fs != null) {
1683                    transferFiles(ftp, fs);
1684                }
1685            }
1686        }
1687
1688        log(transferred + " " + ACTION_TARGET_STRS[action] + " "
1689            + COMPLETED_ACTION_STRS[action]);
1690        if (skipped != 0) {
1691            log(skipped + " " + ACTION_TARGET_STRS[action]
1692                + " were not successfully " + COMPLETED_ACTION_STRS[action]);
1693        }
1694    }
1695
1696
1697    /**
1698     * Correct a file path to correspond to the remote host requirements. This
1699     * implementation currently assumes that the remote end can handle
1700     * Unix-style paths with forward-slash separators. This can be overridden
1701     * with the <code>separator</code> task parameter. No attempt is made to
1702     * determine what syntax is appropriate for the remote host.
1703     *
1704     * @param file the remote file name to be resolved
1705     *
1706     * @return the filename as it will appear on the server.
1707     */

1708    protected String JavaDoc resolveFile(String JavaDoc file) {
1709        return file.replace(System.getProperty("file.separator").charAt(0),
1710            remoteFileSep.charAt(0));
1711    }
1712
1713
1714    /**
1715     * Creates all parent directories specified in a complete relative
1716     * pathname. Attempts to create existing directories will not cause
1717     * errors.
1718     *
1719     * @param ftp the FTP client instance to use to execute FTP actions on
1720     * the remote server.
1721     * @param filename the name of the file whose parents should be created.
1722     * @throws IOException under non documented circumstances
1723     * @throws BuildException if it is impossible to cd to a remote directory
1724     *
1725     */

1726    protected void createParents(FTPClient ftp, String JavaDoc filename)
1727         throws IOException JavaDoc, BuildException {
1728
1729        File JavaDoc dir = new File JavaDoc(filename);
1730        if (dirCache.contains(dir)) {
1731            return;
1732        }
1733
1734
1735        Vector JavaDoc parents = new Vector JavaDoc();
1736        String JavaDoc dirname;
1737
1738        while ((dirname = dir.getParent()) != null) {
1739            File JavaDoc checkDir = new File JavaDoc(dirname);
1740            if (dirCache.contains(checkDir)) {
1741                break;
1742            }
1743            dir = checkDir;
1744            parents.addElement(dir);
1745        }
1746
1747        // find first non cached dir
1748
int i = parents.size() - 1;
1749
1750        if (i >= 0) {
1751            String JavaDoc cwd = ftp.printWorkingDirectory();
1752            String JavaDoc parent = dir.getParent();
1753            if (parent != null) {
1754                if (!ftp.changeWorkingDirectory(resolveFile(parent))) {
1755                    throw new BuildException("could not change to "
1756                        + "directory: " + ftp.getReplyString());
1757                }
1758            }
1759
1760            while (i >= 0) {
1761                dir = (File JavaDoc) parents.elementAt(i--);
1762                // check if dir exists by trying to change into it.
1763
if (!ftp.changeWorkingDirectory(dir.getName())) {
1764                    // could not change to it - try to create it
1765
log("creating remote directory "
1766                        + resolveFile(dir.getPath()), Project.MSG_VERBOSE);
1767                    if (!ftp.makeDirectory(dir.getName())) {
1768                        handleMkDirFailure(ftp);
1769                    }
1770                    if (!ftp.changeWorkingDirectory(dir.getName())) {
1771                        throw new BuildException("could not change to "
1772                            + "directory: " + ftp.getReplyString());
1773                    }
1774                }
1775                dirCache.addElement(dir);
1776            }
1777            ftp.changeWorkingDirectory(cwd);
1778        }
1779    }
1780    /**
1781     * auto find the time difference between local and remote
1782     * @param ftp handle to ftp client
1783     * @return number of millis to add to remote time to make it comparable to local time
1784     * @since ant 1.6
1785     */

1786    private long getTimeDiff(FTPClient ftp) {
1787        long returnValue = 0;
1788        File JavaDoc tempFile = findFileName(ftp);
1789        try {
1790            // create a local temporary file
1791
FILE_UTILS.createNewFile(tempFile);
1792            long localTimeStamp = tempFile.lastModified();
1793            BufferedInputStream JavaDoc instream = new BufferedInputStream JavaDoc(new FileInputStream JavaDoc(tempFile));
1794            ftp.storeFile(tempFile.getName(), instream);
1795            instream.close();
1796            boolean success = FTPReply.isPositiveCompletion(ftp.getReplyCode());
1797            if (success) {
1798                FTPFile [] ftpFiles = ftp.listFiles(tempFile.getName());
1799                if (ftpFiles.length == 1) {
1800                    long remoteTimeStamp = ftpFiles[0].getTimestamp().getTime().getTime();
1801                    returnValue = localTimeStamp - remoteTimeStamp;
1802                }
1803                ftp.deleteFile(ftpFiles[0].getName());
1804            }
1805            // delegate the deletion of the local temp file to the delete task
1806
// because of race conditions occuring on Windows
1807
Delete mydelete = new Delete();
1808            mydelete.bindToOwner(this);
1809            mydelete.setFile(tempFile.getCanonicalFile());
1810            mydelete.execute();
1811        } catch (Exception JavaDoc e) {
1812            throw new BuildException(e, getLocation());
1813        }
1814        return returnValue;
1815    }
1816    /**
1817     * find a suitable name for local and remote temporary file
1818     */

1819    private File JavaDoc findFileName(FTPClient ftp) {
1820        FTPFile [] theFiles = null;
1821        final int maxIterations = 1000;
1822        for (int counter = 1; counter < maxIterations; counter++) {
1823            File JavaDoc localFile = FILE_UTILS.createTempFile("ant" + Integer.toString(counter), ".tmp",
1824                null);
1825            String JavaDoc fileName = localFile.getName();
1826            boolean found = false;
1827            try {
1828                if (counter == 1) {
1829                    theFiles = ftp.listFiles();
1830                }
1831                for (int counter2 = 0; counter2 < theFiles.length; counter2++) {
1832                    if (theFiles[counter2].getName().equals(fileName)) {
1833                        found = true;
1834                        break;
1835                    }
1836                }
1837            } catch (IOException JavaDoc ioe) {
1838                throw new BuildException(ioe, getLocation());
1839            }
1840            if (!found) {
1841                localFile.deleteOnExit();
1842                return localFile;
1843            }
1844        }
1845        return null;
1846    }
1847
1848    private static final SimpleDateFormat JavaDoc TIMESTAMP_LOGGING_SDF =
1849        new SimpleDateFormat JavaDoc("yyyy-MM-dd HH:mm:ss");
1850
1851    /**
1852     * Checks to see if the remote file is current as compared with the local
1853     * file. Returns true if the target file is up to date.
1854     * @param ftp ftpclient
1855     * @param localFile local file
1856     * @param remoteFile remote file
1857     * @return true if the target file is up to date
1858     * @throws IOException in unknown circumstances
1859     * @throws BuildException if the date of the remote files cannot be found and the action is
1860     * GET_FILES
1861     */

1862    protected boolean isUpToDate(FTPClient ftp, File JavaDoc localFile,
1863                                 String JavaDoc remoteFile)
1864         throws IOException JavaDoc, BuildException {
1865        log("checking date for " + remoteFile, Project.MSG_VERBOSE);
1866
1867        FTPFile[] files = ftp.listFiles(remoteFile);
1868
1869        // For Microsoft's Ftp-Service an Array with length 0 is
1870
// returned if configured to return listings in "MS-DOS"-Format
1871
if (files == null || files.length == 0) {
1872            // If we are sending files, then assume out of date.
1873
// If we are getting files, then throw an error
1874

1875            if (action == SEND_FILES) {
1876                log("Could not date test remote file: " + remoteFile
1877                     + "assuming out of date.", Project.MSG_VERBOSE);
1878                return false;
1879            } else {
1880                throw new BuildException("could not date test remote file: "
1881                    + ftp.getReplyString());
1882            }
1883        }
1884
1885        long remoteTimestamp = files[0].getTimestamp().getTime().getTime();
1886        long localTimestamp = localFile.lastModified();
1887        long adjustedRemoteTimestamp =
1888            remoteTimestamp + this.timeDiffMillis + this.granularityMillis;
1889
1890        StringBuffer JavaDoc msg = new StringBuffer JavaDoc(" [")
1891                .append(TIMESTAMP_LOGGING_SDF.format(new Date JavaDoc(localTimestamp)))
1892                .append("] local");
1893        log(msg.toString(), Project.MSG_VERBOSE);
1894
1895        msg = new StringBuffer JavaDoc(" [")
1896                  .append(TIMESTAMP_LOGGING_SDF.format(new Date JavaDoc(adjustedRemoteTimestamp)))
1897                .append("] remote");
1898        if (remoteTimestamp != adjustedRemoteTimestamp) {
1899            msg.append(" - (raw: ")
1900                .append(TIMESTAMP_LOGGING_SDF.format(new Date JavaDoc(remoteTimestamp)))
1901            .append(")");
1902        }
1903        log(msg.toString(), Project.MSG_VERBOSE);
1904
1905
1906
1907        if (this.action == SEND_FILES) {
1908            return adjustedRemoteTimestamp >= localTimestamp;
1909        } else {
1910            return localTimestamp >= adjustedRemoteTimestamp;
1911        }
1912    }
1913
1914
1915    /**
1916    * Sends a site command to the ftp server
1917    * @param ftp ftp client
1918    * @param theCMD command to execute
1919    * @throws IOException in unknown circumstances
1920    * @throws BuildException in unknown circumstances
1921    */

1922    protected void doSiteCommand(FTPClient ftp, String JavaDoc theCMD)
1923         throws IOException JavaDoc, BuildException {
1924        boolean rc;
1925        String JavaDoc[] myReply = null;
1926
1927        log("Doing Site Command: " + theCMD, Project.MSG_VERBOSE);
1928
1929        rc = ftp.sendSiteCommand(theCMD);
1930
1931        if (!rc) {
1932            log("Failed to issue Site Command: " + theCMD, Project.MSG_WARN);
1933        } else {
1934
1935            myReply = ftp.getReplyStrings();
1936
1937            for (int x = 0; x < myReply.length; x++) {
1938                if (myReply[x].indexOf("200") == -1) {
1939                    log(myReply[x], Project.MSG_WARN);
1940                }
1941            }
1942        }
1943    }
1944
1945
1946    /**
1947     * Sends a single file to the remote host. <code>filename</code> may
1948     * contain a relative path specification. When this is the case, <code>sendFile</code>
1949     * will attempt to create any necessary parent directories before sending
1950     * the file. The file will then be sent using the entire relative path
1951     * spec - no attempt is made to change directories. It is anticipated that
1952     * this may eventually cause problems with some FTP servers, but it
1953     * simplifies the coding.
1954     * @param ftp ftp client
1955     * @param dir base directory of the file to be sent (local)
1956     * @param filename relative path of the file to be send
1957     * locally relative to dir
1958     * remotely relative to the remotedir attribute
1959     * @throws IOException in unknown circumstances
1960     * @throws BuildException in unknown circumstances
1961     */

1962    protected void sendFile(FTPClient ftp, String JavaDoc dir, String JavaDoc filename)
1963         throws IOException JavaDoc, BuildException {
1964        InputStream JavaDoc instream = null;
1965
1966        try {
1967            // XXX - why not simply new File(dir, filename)?
1968
File JavaDoc file = getProject().resolveFile(new File JavaDoc(dir, filename).getPath());
1969
1970            if (newerOnly && isUpToDate(ftp, file, resolveFile(filename))) {
1971                return;
1972            }
1973
1974            if (verbose) {
1975                log("transferring " + file.getAbsolutePath());
1976            }
1977
1978            instream = new BufferedInputStream JavaDoc(new FileInputStream JavaDoc(file));
1979
1980            createParents(ftp, filename);
1981
1982            ftp.storeFile(resolveFile(filename), instream);
1983
1984            boolean success = FTPReply.isPositiveCompletion(ftp.getReplyCode());
1985
1986            if (!success) {
1987                String JavaDoc s = "could not put file: " + ftp.getReplyString();
1988
1989                if (skipFailedTransfers) {
1990                    log(s, Project.MSG_WARN);
1991                    skipped++;
1992                } else {
1993                    throw new BuildException(s);
1994                }
1995
1996            } else {
1997                // see if we should issue a chmod command
1998
if (chmod != null) {
1999                    doSiteCommand(ftp, "chmod " + chmod + " " + resolveFile(filename));
2000                }
2001                log("File " + file.getAbsolutePath() + " copied to " + server,
2002                    Project.MSG_VERBOSE);
2003                transferred++;
2004            }
2005        } finally {
2006            if (instream != null) {
2007                try {
2008                    instream.close();
2009                } catch (IOException JavaDoc ex) {
2010                    // ignore it
2011
}
2012            }
2013        }
2014    }
2015
2016
2017    /**
2018     * Delete a file from the remote host.
2019     * @param ftp ftp client
2020     * @param filename file to delete
2021     * @throws IOException in unknown circumstances
2022     * @throws BuildException if skipFailedTransfers is set to false
2023     * and the deletion could not be done
2024     */

2025    protected void delFile(FTPClient ftp, String JavaDoc filename)
2026         throws IOException JavaDoc, BuildException {
2027        if (verbose) {
2028            log("deleting " + filename);
2029        }
2030
2031        if (!ftp.deleteFile(resolveFile(filename))) {
2032            String JavaDoc s = "could not delete file: " + ftp.getReplyString();
2033
2034            if (skipFailedTransfers) {
2035                log(s, Project.MSG_WARN);
2036                skipped++;
2037            } else {
2038                throw new BuildException(s);
2039            }
2040        } else {
2041            log("File " + filename + " deleted from " + server,
2042                Project.MSG_VERBOSE);
2043            transferred++;
2044        }
2045    }
2046
2047    /**
2048     * Delete a directory, if empty, from the remote host.
2049     * @param ftp ftp client
2050     * @param dirname directory to delete
2051     * @throws IOException in unknown circumstances
2052     * @throws BuildException if skipFailedTransfers is set to false
2053     * and the deletion could not be done
2054     */

2055    protected void rmDir(FTPClient ftp, String JavaDoc dirname)
2056         throws IOException JavaDoc, BuildException {
2057        if (verbose) {
2058            log("removing " + dirname);
2059        }
2060
2061        if (!ftp.removeDirectory(resolveFile(dirname))) {
2062            String JavaDoc s = "could not remove directory: " + ftp.getReplyString();
2063
2064            if (skipFailedTransfers) {
2065                log(s, Project.MSG_WARN);
2066                skipped++;
2067            } else {
2068                throw new BuildException(s);
2069            }
2070        } else {
2071            log("Directory " + dirname + " removed from " + server,
2072                Project.MSG_VERBOSE);
2073            transferred++;
2074        }
2075    }
2076
2077
2078    /**
2079     * Retrieve a single file from the remote host. <code>filename</code> may
2080     * contain a relative path specification. <p>
2081     *
2082     * The file will then be retreived using the entire relative path spec -
2083     * no attempt is made to change directories. It is anticipated that this
2084     * may eventually cause problems with some FTP servers, but it simplifies
2085     * the coding.</p>
2086     * @param ftp the ftp client
2087     * @param dir local base directory to which the file should go back
2088     * @param filename relative path of the file based upon the ftp remote directory
2089     * and/or the local base directory (dir)
2090     * @throws IOException in unknown circumstances
2091     * @throws BuildException if skipFailedTransfers is false
2092     * and the file cannot be retrieved.
2093     */

2094    protected void getFile(FTPClient ftp, String JavaDoc dir, String JavaDoc filename)
2095         throws IOException JavaDoc, BuildException {
2096        OutputStream JavaDoc outstream = null;
2097        try {
2098            File JavaDoc file = getProject().resolveFile(new File JavaDoc(dir, filename).getPath());
2099
2100            if (newerOnly && isUpToDate(ftp, file, resolveFile(filename))) {
2101                return;
2102            }
2103
2104            if (verbose) {
2105                log("transferring " + filename + " to "
2106                     + file.getAbsolutePath());
2107            }
2108
2109            File JavaDoc pdir = file.getParentFile();
2110
2111            if (!pdir.exists()) {
2112                pdir.mkdirs();
2113            }
2114            outstream = new BufferedOutputStream JavaDoc(new FileOutputStream JavaDoc(file));
2115            ftp.retrieveFile(resolveFile(filename), outstream);
2116
2117            if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2118                String JavaDoc s = "could not get file: " + ftp.getReplyString();
2119
2120                if (skipFailedTransfers) {
2121                    log(s, Project.MSG_WARN);
2122                    skipped++;
2123                } else {
2124                    throw new BuildException(s);
2125                }
2126
2127            } else {
2128                log("File " + file.getAbsolutePath() + " copied from "
2129                     + server, Project.MSG_VERBOSE);
2130                transferred++;
2131                if (preserveLastModified) {
2132                    outstream.close();
2133                    outstream = null;
2134                    FTPFile[] remote = ftp.listFiles(resolveFile(filename));
2135                    if (remote.length > 0) {
2136                        FILE_UTILS.setFileLastModified(file,
2137                                                      remote[0].getTimestamp()
2138                                                      .getTime().getTime());
2139                    }
2140                }
2141            }
2142        } finally {
2143            if (outstream != null) {
2144                try {
2145                    outstream.close();
2146                } catch (IOException JavaDoc ex) {
2147                    // ignore it
2148
}
2149            }
2150        }
2151    }
2152
2153
2154    /**
2155     * List information about a single file from the remote host. <code>filename</code>
2156     * may contain a relative path specification. <p>
2157     *
2158     * The file listing will then be retrieved using the entire relative path
2159     * spec - no attempt is made to change directories. It is anticipated that
2160     * this may eventually cause problems with some FTP servers, but it
2161     * simplifies the coding.</p>
2162     * @param ftp ftp client
2163     * @param bw buffered writer
2164     * @param filename the directory one wants to list
2165     * @throws IOException in unknown circumstances
2166     * @throws BuildException in unknown circumstances
2167     */

2168    protected void listFile(FTPClient ftp, BufferedWriter JavaDoc bw, String JavaDoc filename)
2169            throws IOException JavaDoc, BuildException {
2170        if (verbose) {
2171            log("listing " + filename);
2172        }
2173        FTPFile[] ftpfiles = ftp.listFiles(resolveFile(filename));
2174
2175        if (ftpfiles != null && ftpfiles.length > 0) {
2176            bw.write(ftpfiles[0].toString());
2177            bw.newLine();
2178            transferred++;
2179        }
2180    }
2181
2182
2183    /**
2184     * Create the specified directory on the remote host.
2185     *
2186     * @param ftp The FTP client connection
2187     * @param dir The directory to create (format must be correct for host
2188     * type)
2189     * @throws IOException in unknown circumstances
2190     * @throws BuildException if ignoreNoncriticalErrors has not been set to true
2191     * and a directory could not be created, for instance because it was
2192     * already existing. Precisely, the codes 521, 550 and 553 will trigger
2193     * a BuildException
2194     */

2195    protected void makeRemoteDir(FTPClient ftp, String JavaDoc dir)
2196         throws IOException JavaDoc, BuildException {
2197        String JavaDoc workingDirectory = ftp.printWorkingDirectory();
2198        if (verbose) {
2199            log("Creating directory: " + dir);
2200        }
2201        if (dir.indexOf("/") == 0) {
2202            ftp.changeWorkingDirectory("/");
2203        }
2204        String JavaDoc subdir = new String JavaDoc();
2205        StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(dir, "/");
2206        while (st.hasMoreTokens()) {
2207            subdir = st.nextToken();
2208            log("Checking " + subdir, Project.MSG_DEBUG);
2209            if (!ftp.changeWorkingDirectory(subdir)) {
2210                if (!ftp.makeDirectory(subdir)) {
2211                    // codes 521, 550 and 553 can be produced by FTP Servers
2212
// to indicate that an attempt to create a directory has
2213
// failed because the directory already exists.
2214
int rc = ftp.getReplyCode();
2215                    if (!(ignoreNoncriticalErrors
2216                        && (rc == FTPReply.CODE_550 || rc == FTPReply.CODE_553
2217                        || rc == CODE_521))) {
2218                        throw new BuildException("could not create directory: "
2219                            + ftp.getReplyString());
2220                    }
2221                    if (verbose) {
2222                        log("Directory already exists");
2223                    }
2224                } else {
2225                    if (verbose) {
2226                        log("Directory created OK");
2227                    }
2228                    ftp.changeWorkingDirectory(subdir);
2229                }
2230            }
2231        }
2232        if (workingDirectory != null) {
2233            ftp.changeWorkingDirectory(workingDirectory);
2234        }
2235    }
2236
2237    /**
2238     * look at the response for a failed mkdir action, decide whether
2239     * it matters or not. If it does, we throw an exception
2240     * @param ftp current ftp connection
2241     * @throws BuildException if this is an error to signal
2242     */

2243    private void handleMkDirFailure(FTPClient ftp)
2244            throws BuildException {
2245        int rc = ftp.getReplyCode();
2246        if (!(ignoreNoncriticalErrors
2247             && (rc == FTPReply.CODE_550 || rc == FTPReply.CODE_553 || rc == CODE_521))) {
2248            throw new BuildException("could not create directory: "
2249                + ftp.getReplyString());
2250        }
2251    }
2252
2253    /**
2254     * Runs the task.
2255     *
2256     * @throws BuildException if the task fails or is not configured
2257     * correctly.
2258     */

2259    public void execute() throws BuildException {
2260        checkAttributes();
2261
2262        FTPClient ftp = null;
2263
2264        try {
2265            log("Opening FTP connection to " + server, Project.MSG_VERBOSE);
2266
2267            ftp = new FTPClient();
2268            if (this.isConfigurationSet) {
2269                ftp = FTPConfigurator.configure(ftp, this);
2270            }
2271
2272            ftp.connect(server, port);
2273            if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2274                throw new BuildException("FTP connection failed: "
2275                     + ftp.getReplyString());
2276            }
2277
2278            log("connected", Project.MSG_VERBOSE);
2279            log("logging in to FTP server", Project.MSG_VERBOSE);
2280
2281            if ((this.account != null && !ftp.login(userid, password, account))
2282                    || (this.account == null && !ftp.login(userid, password))) {
2283                throw new BuildException("Could not login to FTP server");
2284            }
2285
2286            log("login succeeded", Project.MSG_VERBOSE);
2287
2288            if (binary) {
2289                ftp.setFileType(org.apache.commons.net.ftp.FTP.IMAGE_FILE_TYPE);
2290                if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2291                    throw new BuildException("could not set transfer type: "
2292                        + ftp.getReplyString());
2293                }
2294            } else {
2295                ftp.setFileType(org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE);
2296                if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2297                    throw new BuildException("could not set transfer type: "
2298                        + ftp.getReplyString());
2299                }
2300            }
2301
2302            if (passive) {
2303                log("entering passive mode", Project.MSG_VERBOSE);
2304                ftp.enterLocalPassiveMode();
2305                if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2306                    throw new BuildException("could not enter into passive "
2307                         + "mode: " + ftp.getReplyString());
2308                }
2309            }
2310
2311            // If an initial command was configured then send it.
2312
// Some FTP servers offer different modes of operation,
2313
// E.G. switching between a UNIX file system mode and
2314
// a legacy file system.
2315
if (this.initialSiteCommand != null) {
2316                RetryHandler h = new RetryHandler(this.retriesAllowed, this);
2317                final FTPClient lftp = ftp;
2318                executeRetryable(h, new Retryable() {
2319                    public void execute() throws IOException JavaDoc {
2320                        doSiteCommand(lftp, FTP.this.initialSiteCommand);
2321                    }
2322                }, "initial site command: " + this.initialSiteCommand);
2323            }
2324
2325
2326            // For a unix ftp server you can set the default mask for all files
2327
// created.
2328

2329            if (umask != null) {
2330                RetryHandler h = new RetryHandler(this.retriesAllowed, this);
2331                final FTPClient lftp = ftp;
2332                executeRetryable(h, new Retryable() {
2333                    public void execute() throws IOException JavaDoc {
2334                        doSiteCommand(lftp, "umask " + umask);
2335                    }
2336                }, "umask " + umask);
2337            }
2338
2339            // If the action is MK_DIR, then the specified remote
2340
// directory is the directory to create.
2341

2342            if (action == MK_DIR) {
2343                RetryHandler h = new RetryHandler(this.retriesAllowed, this);
2344                final FTPClient lftp = ftp;
2345                executeRetryable(h, new Retryable() {
2346                    public void execute() throws IOException JavaDoc {
2347                        makeRemoteDir(lftp, remotedir);
2348                    }
2349                }, remotedir);
2350            } else if (action == SITE_CMD) {
2351                    RetryHandler h = new RetryHandler(this.retriesAllowed, this);
2352                    final FTPClient lftp = ftp;
2353                    executeRetryable(h, new Retryable() {
2354                        public void execute() throws IOException JavaDoc {
2355                            doSiteCommand(lftp, FTP.this.siteCommand);
2356                        }
2357                    }, "Site Command: " + this.siteCommand);
2358            } else {
2359                if (remotedir != null) {
2360                    log("changing the remote directory", Project.MSG_VERBOSE);
2361                    ftp.changeWorkingDirectory(remotedir);
2362                    if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2363                        throw new BuildException("could not change remote "
2364                             + "directory: " + ftp.getReplyString());
2365                    }
2366                }
2367                if (newerOnly && timeDiffAuto) {
2368                // in this case we want to find how much time span there is between local
2369
// and remote
2370
timeDiffMillis = getTimeDiff(ftp);
2371                }
2372                log(ACTION_STRS[action] + " " + ACTION_TARGET_STRS[action]);
2373                transferFiles(ftp);
2374            }
2375
2376        } catch (IOException JavaDoc ex) {
2377            throw new BuildException("error during FTP transfer: " + ex, ex);
2378        } finally {
2379            if (ftp != null && ftp.isConnected()) {
2380                try {
2381                    log("disconnecting", Project.MSG_VERBOSE);
2382                    ftp.logout();
2383                    ftp.disconnect();
2384                } catch (IOException JavaDoc ex) {
2385                    // ignore it
2386
}
2387            }
2388        }
2389    }
2390
2391
2392    /**
2393     * an action to perform, one of
2394     * "send", "put", "recv", "get", "del", "delete", "list", "mkdir", "chmod",
2395     * "rmdir"
2396     */

2397    public static class Action extends EnumeratedAttribute {
2398
2399        private static final String JavaDoc[] VALID_ACTIONS = {
2400            "send", "put", "recv", "get", "del", "delete", "list", "mkdir",
2401            "chmod", "rmdir", "site"
2402            };
2403
2404
2405        /**
2406         * Get the valid values
2407         *
2408         * @return an array of the valid FTP actions.
2409         */

2410        public String JavaDoc[] getValues() {
2411            return VALID_ACTIONS;
2412        }
2413
2414
2415        /**
2416         * Get the symbolic equivalent of the action value.
2417         *
2418         * @return the SYMBOL representing the given action.
2419         */

2420        public int getAction() {
2421            String JavaDoc actionL = getValue().toLowerCase(Locale.US);
2422
2423            if (actionL.equals("send") || actionL.equals("put")) {
2424                return SEND_FILES;
2425            } else if (actionL.equals("recv") || actionL.equals("get")) {
2426                return GET_FILES;
2427            } else if (actionL.equals("del") || actionL.equals("delete")) {
2428                return DEL_FILES;
2429            } else if (actionL.equals("list")) {
2430                return LIST_FILES;
2431            } else if (actionL.equals("chmod")) {
2432                return CHMOD;
2433            } else if (actionL.equals("mkdir")) {
2434                return MK_DIR;
2435            } else if (actionL.equals("rmdir")) {
2436                return RM_DIR;
2437            } else if (actionL.equals("site")) {
2438                return SITE_CMD;
2439            }
2440            return SEND_FILES;
2441        }
2442    }
2443    /**
2444     * represents one of the valid timestamp adjustment values
2445     * recognized by the <code>timestampGranularity</code> attribute.<p>
2446
2447     * A timestamp adjustment may be used in file transfers for checking
2448     * uptodateness. MINUTE means to add one minute to the server
2449     * timestamp. This is done because FTP servers typically list
2450     * timestamps HH:mm and client FileSystems typically use HH:mm:ss.
2451     *
2452     * The default is to use MINUTE for PUT actions and NONE for GET
2453     * actions, since GETs have the <code>preserveLastModified</code>
2454     * option, which takes care of the problem in most use cases where
2455     * this level of granularity is an issue.
2456     *
2457     */

2458    public static class Granularity extends EnumeratedAttribute {
2459
2460        private static final String JavaDoc[] VALID_GRANULARITIES = {
2461                "", "MINUTE", "NONE"
2462        };
2463
2464        /**
2465         * Get the valid values.
2466         * @return the list of valid Granularity values
2467         */

2468        public String JavaDoc[] getValues() {
2469            // TODO Auto-generated method stub
2470
return VALID_GRANULARITIES;
2471        }
2472        /**
2473         * returns the number of milliseconds associated with
2474         * the attribute, which can vary in some cases depending
2475         * on the value of the action parameter.
2476         * @param action SEND_FILES or GET_FILES
2477         * @return the number of milliseconds associated with
2478         * the attribute, in the context of the supplied action
2479         */

2480        public long getMilliseconds(int action) {
2481            String JavaDoc granularityU = getValue().toUpperCase(Locale.US);
2482
2483            if ("".equals(granularityU)) {
2484                if (action == SEND_FILES) {
2485                    return GRANULARITY_MINUTE;
2486                }
2487            } else if ("MINUTE".equals(granularityU)) {
2488                return GRANULARITY_MINUTE;
2489                }
2490                return 0L;
2491        }
2492        static final Granularity getDefault() {
2493            Granularity g = new Granularity();
2494            g.setValue("");
2495            return g;
2496        }
2497
2498   }
2499   /**
2500         * one of the valid system type keys recognized by the systemTypeKey
2501         * attribute.
2502         */

2503    public static class FTPSystemType extends EnumeratedAttribute {
2504
2505       private static final String JavaDoc[] VALID_SYSTEM_TYPES = {
2506                   "", "UNIX", "VMS", "WINDOWS", "OS/2", "OS/400",
2507                   "MVS"
2508           };
2509
2510
2511            /**
2512             * Get the valid values.
2513             * @return the list of valid system types.
2514             */

2515            public String JavaDoc[] getValues() {
2516                return VALID_SYSTEM_TYPES;
2517            }
2518
2519            static final FTPSystemType getDefault() {
2520                FTPSystemType ftpst = new FTPSystemType();
2521                ftpst.setValue("");
2522                return ftpst;
2523            }
2524    }
2525    /**
2526     * Enumerated class for languages.
2527     */

2528    public static class LanguageCode extends EnumeratedAttribute {
2529
2530
2531        private static final String JavaDoc[] VALID_LANGUAGE_CODES =
2532            getValidLanguageCodes();
2533
2534        private static String JavaDoc[] getValidLanguageCodes() {
2535            Collection JavaDoc c = FTPClientConfig.getSupportedLanguageCodes();
2536            String JavaDoc[] ret = new String JavaDoc[c.size() + 1];
2537            int i = 0;
2538            ret[i++] = "";
2539            for (Iterator JavaDoc it = c.iterator(); it.hasNext(); i++) {
2540                ret[i] = (String JavaDoc) it.next();
2541            }
2542            return ret;
2543        }
2544
2545
2546             /**
2547              * Return the value values.
2548              * @return the list of valid language types.
2549              */

2550             public String JavaDoc[] getValues() {
2551                 return VALID_LANGUAGE_CODES;
2552             }
2553
2554             static final LanguageCode getDefault() {
2555                 LanguageCode lc = new LanguageCode();
2556                 lc.setValue("");
2557                 return lc;
2558             }
2559     }
2560
2561}
2562
Popular Tags