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