View Javadoc

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  package org.apache.commons.vfs.provider.ftp;
18  
19  import org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  import org.apache.commons.net.ftp.FTPFile;
22  import org.apache.commons.vfs.FileName;
23  import org.apache.commons.vfs.FileObject;
24  import org.apache.commons.vfs.FileSystemException;
25  import org.apache.commons.vfs.FileType;
26  import org.apache.commons.vfs.RandomAccessContent;
27  import org.apache.commons.vfs.provider.AbstractFileObject;
28  import org.apache.commons.vfs.provider.UriParser;
29  import org.apache.commons.vfs.util.Messages;
30  import org.apache.commons.vfs.util.MonitorInputStream;
31  import org.apache.commons.vfs.util.MonitorOutputStream;
32  import org.apache.commons.vfs.util.RandomAccessMode;
33  import org.apache.commons.vfs.util.FileObjectUtils;
34  
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.io.OutputStream;
38  import java.util.Calendar;
39  import java.util.Collections;
40  import java.util.Iterator;
41  import java.util.Map;
42  import java.util.TreeMap;
43  
44  /***
45   * An FTP file.
46   *
47   * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
48   * @version $Revision: 484943 $ $Date: 2006-12-09 08:42:06 +0100 (Sa, 09 Dez 2006) $
49   */
50  public class FtpFileObject
51      extends AbstractFileObject
52  {
53      private Log log = LogFactory.getLog(FtpFileObject.class);
54  
55      private static final Map EMPTY_FTP_FILE_MAP = Collections.unmodifiableMap(new TreeMap());
56  
57      private final FtpFileSystem ftpFs;
58      private final String relPath;
59  
60      // Cached info
61      private FTPFile fileInfo;
62      private Map children;
63      private FileObject linkDestination;
64  
65  	private boolean inRefresh=false;
66  
67  	protected FtpFileObject(final FileName name,
68                              final FtpFileSystem fileSystem,
69                              final FileName rootName)
70          throws FileSystemException
71      {
72          super(name, fileSystem);
73          ftpFs = fileSystem;
74          String relPath = UriParser.decode(rootName.getRelativeName(name));
75          if (".".equals(relPath))
76          {
77              // do not use the "." as path against the ftp-server
78              // e.g. the uu.net ftp-server do a recursive listing then
79              // this.relPath = UriParser.decode(rootName.getPath());
80              // this.relPath = ".";
81              this.relPath = null;
82          }
83          else
84          {
85              this.relPath = relPath;
86          }
87      }
88  
89      /***
90       * Called by child file objects, to locate their ftp file info.
91       *
92       * @param name  the filename in its native form ie. without uri stuff (%nn)
93       * @param flush recreate children cache
94       */
95      private FTPFile getChildFile(final String name, final boolean flush) throws IOException
96      {
97          if (flush)
98          {
99   			children = null;
100         }
101 
102         // List the children of this file
103         doGetChildren();
104 
105         // Look for the requested child
106         FTPFile ftpFile = (FTPFile) children.get(name);
107         return ftpFile;
108     }
109 
110     /***
111      * Fetches the children of this file, if not already cached.
112      */
113     private void doGetChildren() throws IOException
114     {
115         if (children != null)
116         {
117             return;
118         }
119 
120         final FtpClient client = ftpFs.getClient();
121         try
122         {
123             final FTPFile[] tmpChildren = client.listFiles(relPath);
124             if (tmpChildren == null || tmpChildren.length == 0)
125             {
126                 children = EMPTY_FTP_FILE_MAP;
127             }
128             else
129             {
130                 children = new TreeMap();
131 
132                 // Remove '.' and '..' elements
133                 for (int i = 0; i < tmpChildren.length; i++)
134                 {
135                     final FTPFile child = tmpChildren[i];
136                     if (child == null)
137                     {
138                         if (log.isDebugEnabled())
139                         {
140                             log.debug(Messages.getString("vfs.provider.ftp/invalid-directory-entry.debug",
141                                 new Object[]
142                                     {
143                                         new Integer(i), relPath
144                                     }));
145                         }
146                         continue;
147                     }
148                     if (!".".equals(child.getName())
149                         && !"..".equals(child.getName()))
150                     {
151                         children.put(child.getName(), child);
152                     }
153                 }
154             }
155         }
156         finally
157         {
158             ftpFs.putClient(client);
159         }
160     }
161 
162     /***
163      * Attaches this file object to its file resource.
164      */
165     protected void doAttach()
166         throws IOException
167     {
168         // Get the parent folder to find the info for this file
169         getInfo(false);
170     }
171 
172     /***
173      * Fetches the info for this file.
174      */
175     private void getInfo(boolean flush) throws IOException
176     {
177         final FtpFileObject parent = (FtpFileObject) FileObjectUtils.getAbstractFileObject(getParent());
178         FTPFile newFileInfo;
179         if (parent != null)
180         {
181             newFileInfo = parent.getChildFile(UriParser.decode(getName().getBaseName()), flush);
182         }
183         else
184         {
185             // Assume the root is a directory and exists
186             newFileInfo = new FTPFile();
187             newFileInfo.setType(FTPFile.DIRECTORY_TYPE);
188         }
189 
190         this.fileInfo = newFileInfo;
191     }
192 
193 	/***
194 	 *
195 	 * @throws FileSystemException
196 	 */
197 	public void refresh() throws FileSystemException
198 	{
199 		if (!inRefresh)
200 		{
201 			try
202 			{
203 				inRefresh = true;
204 				super.refresh();
205 				try
206 				{
207 					// this will tell the parent to recreate its children collection
208 					getInfo(true);
209 				}
210 				catch (IOException e)
211 				{
212 					throw new FileSystemException(e);
213 				}
214 			}
215 			finally
216 			{
217 				inRefresh = false;
218 			}
219 		}
220 	}
221 
222 	/***
223      * Detaches this file object from its file resource.
224      */
225     protected void doDetach()
226     {
227         this.fileInfo = null;
228         children = null;
229     }
230 
231     /***
232      * Called when the children of this file change.
233      */
234     protected void onChildrenChanged(FileName child, FileType newType)
235     {
236         if (children != null && newType.equals(FileType.IMAGINARY))
237         {
238             try
239             {
240                 children.remove(UriParser.decode(child.getBaseName()));
241             }
242             catch (FileSystemException e)
243             {
244                 throw new RuntimeException(e.getMessage());
245             }
246         }
247         else
248         {
249             // if child was added we have to rescan the children
250             // TODO - get rid of this
251             children = null;
252         }
253     }
254 
255     /***
256      * Called when the type or content of this file changes.
257      */
258     protected void onChange() throws IOException
259     {
260         children = null;
261 
262         if (getType().equals(FileType.IMAGINARY))
263         {
264             // file is deleted, avoid server lookup
265             this.fileInfo = null;
266             return;
267         }
268 
269         getInfo(true);
270     }
271 
272     /***
273      * Determines the type of the file, returns null if the file does not
274      * exist.
275      */
276     protected FileType doGetType()
277         throws Exception
278     {
279         if (this.fileInfo == null)
280         {
281             return FileType.IMAGINARY;
282         }
283         else if (this.fileInfo.isDirectory())
284         {
285             return FileType.FOLDER;
286         }
287         else if (this.fileInfo.isFile())
288         {
289             return FileType.FILE;
290         }
291         else if (this.fileInfo.isSymbolicLink())
292         {
293             return getLinkDestination().getType();
294         }
295 
296         throw new FileSystemException("vfs.provider.ftp/get-type.error", getName());
297     }
298 
299     private FileObject getLinkDestination() throws FileSystemException
300     {
301         if (linkDestination == null)
302         {
303             final String path = this.fileInfo.getLink();
304             FileName relativeTo = getName().getParent();
305             if (relativeTo == null)
306             {
307                 relativeTo = getName();
308             }
309             FileName linkDestinationName = getFileSystem().getFileSystemManager().resolveName(relativeTo, path);
310             linkDestination = getFileSystem().resolveFile(linkDestinationName);
311         }
312 
313         return linkDestination;
314     }
315 
316     protected FileObject[] doListChildrenResolved() throws Exception
317     {
318         if (this.fileInfo.isSymbolicLink())
319         {
320             return getLinkDestination().getChildren();
321         }
322 
323         return null;
324     }
325 
326     /***
327      * Lists the children of the file.
328      */
329     protected String[] doListChildren()
330         throws Exception
331     {
332         // List the children of this file
333         doGetChildren();
334 
335         // TODO - get rid of this children stuff
336         final String[] childNames = new String[children.size()];
337         int childNum = -1;
338         Iterator iterChildren = children.values().iterator();
339         while (iterChildren.hasNext())
340         {
341             childNum++;
342             final FTPFile child = (FTPFile) iterChildren.next();
343             childNames[childNum] = child.getName();
344         }
345 
346         return UriParser.encode(childNames);
347     }
348 
349     /***
350      * Deletes the file.
351      */
352     protected void doDelete() throws Exception
353     {
354         final boolean ok;
355         final FtpClient ftpClient = ftpFs.getClient();
356         try
357         {
358             if (this.fileInfo.isDirectory())
359             {
360                 ok = ftpClient.removeDirectory(relPath);
361             }
362             else
363             {
364                 ok = ftpClient.deleteFile(relPath);
365             }
366         }
367         finally
368         {
369             ftpFs.putClient(ftpClient);
370         }
371 
372         if (!ok)
373         {
374             throw new FileSystemException("vfs.provider.ftp/delete-file.error", getName());
375         }
376         this.fileInfo = null;
377         children = EMPTY_FTP_FILE_MAP;
378     }
379 
380     /***
381      * Renames the file
382      */
383     protected void doRename(FileObject newfile) throws Exception
384     {
385         final boolean ok;
386         final FtpClient ftpClient = ftpFs.getClient();
387         try
388         {
389             String oldName = getName().getPath();
390             String newName = newfile.getName().getPath();
391             ok = ftpClient.rename(oldName, newName);
392         }
393         finally
394         {
395             ftpFs.putClient(ftpClient);
396         }
397 
398         if (!ok)
399         {
400             throw new FileSystemException("vfs.provider.ftp/rename-file.error", new Object[]{getName().toString(), newfile});
401         }
402         this.fileInfo = null;
403         children = EMPTY_FTP_FILE_MAP;
404     }
405 
406     /***
407      * Creates this file as a folder.
408      */
409     protected void doCreateFolder()
410         throws Exception
411     {
412         final boolean ok;
413         final FtpClient client = ftpFs.getClient();
414         try
415         {
416             ok = client.makeDirectory(relPath);
417         }
418         finally
419         {
420             ftpFs.putClient(client);
421         }
422 
423         if (!ok)
424         {
425             throw new FileSystemException("vfs.provider.ftp/create-folder.error", getName());
426         }
427     }
428 
429     /***
430      * Returns the size of the file content (in bytes).
431      */
432     protected long doGetContentSize() throws Exception
433     {
434         if (this.fileInfo.isSymbolicLink())
435         {
436             return getLinkDestination().getContent().getSize();
437         }
438         else
439         {
440             return this.fileInfo.getSize();
441         }
442     }
443 
444     /***
445      * get the last modified time on an ftp file
446      *
447      * @see org.apache.commons.vfs.provider.AbstractFileObject#doGetLastModifiedTime()
448      */
449     protected long doGetLastModifiedTime() throws Exception
450     {
451         if (this.fileInfo.isSymbolicLink())
452         {
453             return getLinkDestination().getContent().getLastModifiedTime();
454         }
455         else
456         {
457             Calendar timestamp = this.fileInfo.getTimestamp();
458             if (timestamp == null)
459             {
460                 return 0L;
461             }
462             else
463             {
464                 return (timestamp.getTime().getTime());
465             }
466         }
467     }
468 
469     /***
470      * Creates an input stream to read the file content from.
471      */
472     protected InputStream doGetInputStream() throws Exception
473     {
474         final FtpClient client = ftpFs.getClient();
475         final InputStream instr = client.retrieveFileStream(relPath);
476         return new FtpInputStream(client, instr);
477     }
478 
479     protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception
480     {
481         return new FtpRandomAccessContent(this, mode);
482     }
483 
484     /***
485      * Creates an output stream to write the file content to.
486      */
487     protected OutputStream doGetOutputStream(boolean bAppend)
488         throws Exception
489     {
490         final FtpClient client = ftpFs.getClient();
491         OutputStream out = null;
492         if (bAppend)
493         {
494             out = client.appendFileStream(relPath);
495         }
496         else
497         {
498             out = client.storeFileStream(relPath);
499         }
500 
501         if (out == null)
502         {
503             throw new FileSystemException("vfs.provider.ftp/output-error.debug", new Object[]
504                 {
505                     this.getName(),
506                     client.getReplyString()
507                 });
508         }
509 
510         return new FtpOutputStream(client, out);
511     }
512 
513     String getRelPath()
514     {
515         return relPath;
516     }
517 
518     FtpInputStream getInputStream(long filePointer) throws IOException
519     {
520         final FtpClient client = ftpFs.getClient();
521         final InputStream instr = client.retrieveFileStream(relPath, filePointer);
522         if (instr == null)
523         {
524             throw new FileSystemException("vfs.provider.ftp/input-error.debug", new Object[]
525                 {
526                     this.getName(),
527                     client.getReplyString()
528                 });
529         }
530         return new FtpInputStream(client, instr);
531     }
532 
533     /***
534      * An InputStream that monitors for end-of-file.
535      */
536     class FtpInputStream
537         extends MonitorInputStream
538     {
539         private final FtpClient client;
540 
541         public FtpInputStream(final FtpClient client, final InputStream in)
542         {
543             super(in);
544             this.client = client;
545         }
546 
547         void abort() throws IOException
548         {
549             client.abort();
550             close();
551         }
552 
553         /***
554          * Called after the stream has been closed.
555          */
556         protected void onClose() throws IOException
557         {
558             final boolean ok;
559             try
560             {
561                 ok = client.completePendingCommand();
562             }
563             finally
564             {
565                 ftpFs.putClient(client);
566             }
567 
568             if (!ok)
569             {
570                 throw new FileSystemException("vfs.provider.ftp/finish-get.error", getName());
571             }
572         }
573     }
574 
575     /***
576      * An OutputStream that monitors for end-of-file.
577      */
578     private class FtpOutputStream
579         extends MonitorOutputStream
580     {
581         private final FtpClient client;
582 
583         public FtpOutputStream(final FtpClient client, final OutputStream outstr)
584         {
585             super(outstr);
586             this.client = client;
587         }
588 
589         /***
590          * Called after this stream is closed.
591          */
592         protected void onClose() throws IOException
593         {
594             final boolean ok;
595             try
596             {
597                 ok = client.completePendingCommand();
598             }
599             finally
600             {
601                 ftpFs.putClient(client);
602             }
603 
604             if (!ok)
605             {
606                 throw new FileSystemException("vfs.provider.ftp/finish-put.error", getName());
607             }
608         }
609     }
610 }