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.vfs2.provider.ftp;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.commons.net.ftp.FTPClient;
26  import org.apache.commons.net.ftp.FTPFile;
27  import org.apache.commons.net.ftp.FTPReply;
28  import org.apache.commons.vfs2.FileSystemException;
29  import org.apache.commons.vfs2.FileSystemOptions;
30  import org.apache.commons.vfs2.UserAuthenticationData;
31  import org.apache.commons.vfs2.provider.GenericFileName;
32  import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
33  
34  /**
35   * A wrapper to the FTPClient to allow automatic reconnect on connection loss.
36   * <p>
37   * I decided to not to use eg. noop() to determine the state of the connection to avoid
38   * unnecessary server round-trips.
39   */
40  public class FTPClientWrapper implements FtpClient
41  {
42      
43      private static final Log LOG = LogFactory.getLog(FTPClientWrapper.class);
44      
45      protected final FileSystemOptions fileSystemOptions;
46      private final GenericFileName root;
47      private FTPClient ftpClient;
48  
49      protected FTPClientWrapper(final GenericFileName root, final FileSystemOptions fileSystemOptions)
50          throws FileSystemException
51      {
52          this.root = root;
53          this.fileSystemOptions = fileSystemOptions;
54          getFtpClient(); // fail-fast
55      }
56  
57      public GenericFileName getRoot()
58      {
59          return root;
60      }
61  
62      public FileSystemOptions getFileSystemOptions()
63      {
64          return fileSystemOptions;
65      }
66  
67      private FTPClient createClient() throws FileSystemException
68      {
69          final GenericFileName rootName = getRoot();
70  
71          UserAuthenticationData authData = null;
72          try
73          {
74              authData = UserAuthenticatorUtils.authenticate(fileSystemOptions, FtpFileProvider.AUTHENTICATOR_TYPES);
75  
76              return createClient(rootName, authData);
77          }
78          finally
79          {
80              UserAuthenticatorUtils.cleanup(authData);
81          }
82      }
83  
84      protected FTPClient createClient(final GenericFileName rootName, final UserAuthenticationData authData)
85          throws FileSystemException
86      {
87          return FtpClientFactory.createConnection(
88              rootName.getHostName(),
89              rootName.getPort(),
90              UserAuthenticatorUtils.getData(
91                  authData, UserAuthenticationData.USERNAME, UserAuthenticatorUtils.toChar(rootName.getUserName())),
92              UserAuthenticatorUtils.getData(
93                  authData, UserAuthenticationData.PASSWORD, UserAuthenticatorUtils.toChar(rootName.getPassword())),
94              rootName.getPath(), getFileSystemOptions());
95      }
96  
97      private FTPClient getFtpClient() throws FileSystemException
98      {
99          if (ftpClient == null)
100         {
101             ftpClient = createClient();
102         }
103 
104         return ftpClient;
105     }
106 
107     @Override
108     public boolean isConnected() throws FileSystemException
109     {
110         return ftpClient != null && ftpClient.isConnected();
111     }
112 
113     @Override
114     public void disconnect() throws IOException
115     {
116         try
117         {
118             getFtpClient().quit();
119         }
120         catch (IOException e)
121         {
122             LOG.debug("I/O exception while trying to quit, probably it's a timed out connection, ignoring.", e);
123         }
124         finally
125         {
126             try
127             {
128                 getFtpClient().disconnect();
129             }
130             catch (IOException e)
131             {
132                 LOG.warn("I/O exception while trying to disconnect, probably it's a closed connection, ignoring.", e);
133             }
134             finally
135             {
136                 ftpClient = null;
137             }
138         }
139     }
140 
141     @Override
142     public FTPFile[] listFiles(final String relPath) throws IOException
143     {
144         try
145         {
146             // VFS-210: return getFtpClient().listFiles(relPath);
147             final FTPFile[] files = listFilesInDirectory(relPath);
148             return files;
149         }
150         catch (final IOException e)
151         {
152             disconnect();
153             final FTPFile[] files = listFilesInDirectory(relPath);
154             return files;
155         }
156     }
157 
158     private FTPFile[] listFilesInDirectory(final String relPath) throws IOException
159     {
160         FTPFile[] files;
161 
162         // VFS-307: no check if we can simply list the files, this might fail if there are spaces in the path
163         files = getFtpClient().listFiles(relPath);
164         if (FTPReply.isPositiveCompletion(getFtpClient().getReplyCode()))
165         {
166             return files;
167         }
168 
169         // VFS-307: now try the hard way by cd'ing into the directory, list and cd back
170         // if VFS is required to fallback here the user might experience a real bad FTP performance
171         // as then every list requires 4 ftp commands.
172         String workingDirectory = null;
173         if (relPath != null)
174         {
175             workingDirectory = getFtpClient().printWorkingDirectory();
176             if (!getFtpClient().changeWorkingDirectory(relPath))
177             {
178                 return null;
179             }
180         }
181 
182         files = getFtpClient().listFiles();
183 
184         if (relPath != null && !getFtpClient().changeWorkingDirectory(workingDirectory))
185         {
186             throw new FileSystemException("vfs.provider.ftp.wrapper/change-work-directory-back.error",
187                     workingDirectory);
188         }
189         return files;
190     }
191 
192     @Override
193     public boolean removeDirectory(final String relPath) throws IOException
194     {
195         try
196         {
197             return getFtpClient().removeDirectory(relPath);
198         }
199         catch (final IOException e)
200         {
201             disconnect();
202             return getFtpClient().removeDirectory(relPath);
203         }
204     }
205 
206     @Override
207     public boolean deleteFile(final String relPath) throws IOException
208     {
209         try
210         {
211             return getFtpClient().deleteFile(relPath);
212         }
213         catch (final IOException e)
214         {
215             disconnect();
216             return getFtpClient().deleteFile(relPath);
217         }
218     }
219 
220     @Override
221     public boolean rename(final String oldName, final String newName) throws IOException
222     {
223         try
224         {
225             return getFtpClient().rename(oldName, newName);
226         }
227         catch (final IOException e)
228         {
229             disconnect();
230             return getFtpClient().rename(oldName, newName);
231         }
232     }
233 
234     @Override
235     public boolean makeDirectory(final String relPath) throws IOException
236     {
237         try
238         {
239             return getFtpClient().makeDirectory(relPath);
240         }
241         catch (final IOException e)
242         {
243             disconnect();
244             return getFtpClient().makeDirectory(relPath);
245         }
246     }
247 
248     @Override
249     public boolean completePendingCommand() throws IOException
250     {
251         if (ftpClient != null)
252         {
253             return getFtpClient().completePendingCommand();
254         }
255 
256         return true;
257     }
258 
259     @Override
260     public InputStream retrieveFileStream(final String relPath) throws IOException
261     {
262         try
263         {
264             return getFtpClient().retrieveFileStream(relPath);
265         }
266         catch (final IOException e)
267         {
268             disconnect();
269             return getFtpClient().retrieveFileStream(relPath);
270         }
271     }
272 
273     @Override
274     public InputStream retrieveFileStream(final String relPath, final long restartOffset) throws IOException
275     {
276         try
277         {
278             final FTPClient client = getFtpClient();
279             client.setRestartOffset(restartOffset);
280             return client.retrieveFileStream(relPath);
281         }
282         catch (final IOException e)
283         {
284             disconnect();
285             final FTPClient client = getFtpClient();
286             client.setRestartOffset(restartOffset);
287             return client.retrieveFileStream(relPath);
288         }
289     }
290 
291     @Override
292     public OutputStream appendFileStream(final String relPath) throws IOException
293     {
294         try
295         {
296             return getFtpClient().appendFileStream(relPath);
297         }
298         catch (final IOException e)
299         {
300             disconnect();
301             return getFtpClient().appendFileStream(relPath);
302         }
303     }
304 
305     @Override
306     public OutputStream storeFileStream(final String relPath) throws IOException
307     {
308         try
309         {
310             return getFtpClient().storeFileStream(relPath);
311         }
312         catch (final IOException e)
313         {
314             disconnect();
315             return getFtpClient().storeFileStream(relPath);
316         }
317     }
318 
319     @Override
320     public boolean abort() throws IOException
321     {
322         try
323         {
324             // imario@apache.org: 2005-02-14
325             // it should be better to really "abort" the transfer, but
326             // currently I didnt manage to make it work - so lets "abort" the hard way.
327             // return getFtpClient().abort();
328 
329             disconnect();
330             return true;
331         }
332         catch (final IOException e)
333         {
334             disconnect();
335         }
336         return true;
337     }
338 
339     @Override
340     public String getReplyString() throws IOException
341     {
342         return getFtpClient().getReplyString();
343     }
344 }