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 }