001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.provider.ftp; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025import org.apache.commons.net.ftp.FTPClient; 026import org.apache.commons.net.ftp.FTPFile; 027import org.apache.commons.net.ftp.FTPReply; 028import org.apache.commons.vfs2.FileSystemException; 029import org.apache.commons.vfs2.FileSystemOptions; 030import org.apache.commons.vfs2.UserAuthenticationData; 031import org.apache.commons.vfs2.provider.GenericFileName; 032import org.apache.commons.vfs2.util.UserAuthenticatorUtils; 033 034/** 035 * A wrapper to the FTPClient to allow automatic reconnect on connection loss. 036 * <p> 037 * I decided to not to use eg. noop() to determine the state of the connection to avoid unnecessary server round-trips. 038 */ 039public class FTPClientWrapper implements FtpClient { 040 041 private static final Log LOG = LogFactory.getLog(FTPClientWrapper.class); 042 043 protected final FileSystemOptions fileSystemOptions; 044 private final GenericFileName root; 045 private FTPClient ftpClient; 046 047 protected FTPClientWrapper(final GenericFileName root, final FileSystemOptions fileSystemOptions) 048 throws FileSystemException { 049 this.root = root; 050 this.fileSystemOptions = fileSystemOptions; 051 getFtpClient(); // fail-fast 052 } 053 054 public GenericFileName getRoot() { 055 return root; 056 } 057 058 public FileSystemOptions getFileSystemOptions() { 059 return fileSystemOptions; 060 } 061 062 private FTPClient createClient() throws FileSystemException { 063 final GenericFileName rootName = getRoot(); 064 065 UserAuthenticationData authData = null; 066 try { 067 authData = UserAuthenticatorUtils.authenticate(fileSystemOptions, FtpFileProvider.AUTHENTICATOR_TYPES); 068 069 return createClient(rootName, authData); 070 } finally { 071 UserAuthenticatorUtils.cleanup(authData); 072 } 073 } 074 075 protected FTPClient createClient(final GenericFileName rootName, final UserAuthenticationData authData) 076 throws FileSystemException { 077 return FtpClientFactory.createConnection(rootName.getHostName(), rootName.getPort(), 078 UserAuthenticatorUtils.getData(authData, UserAuthenticationData.USERNAME, 079 UserAuthenticatorUtils.toChar(rootName.getUserName())), 080 UserAuthenticatorUtils.getData(authData, UserAuthenticationData.PASSWORD, 081 UserAuthenticatorUtils.toChar(rootName.getPassword())), 082 rootName.getPath(), getFileSystemOptions()); 083 } 084 085 private FTPClient getFtpClient() throws FileSystemException { 086 if (ftpClient == null) { 087 ftpClient = createClient(); 088 } 089 090 return ftpClient; 091 } 092 093 @Override 094 public boolean isConnected() throws FileSystemException { 095 return ftpClient != null && ftpClient.isConnected(); 096 } 097 098 @Override 099 public void disconnect() throws IOException { 100 try { 101 getFtpClient().quit(); 102 } catch (final IOException e) { 103 LOG.debug("I/O exception while trying to quit, probably it's a timed out connection, ignoring.", e); 104 } finally { 105 try { 106 getFtpClient().disconnect(); 107 } catch (final IOException e) { 108 LOG.warn("I/O exception while trying to disconnect, probably it's a closed connection, ignoring.", e); 109 } finally { 110 ftpClient = null; 111 } 112 } 113 } 114 115 @Override 116 public FTPFile[] listFiles(final String relPath) throws IOException { 117 try { 118 // VFS-210: return getFtpClient().listFiles(relPath); 119 final FTPFile[] files = listFilesInDirectory(relPath); 120 return files; 121 } catch (final IOException e) { 122 disconnect(); 123 final FTPFile[] files = listFilesInDirectory(relPath); 124 return files; 125 } 126 } 127 128 private FTPFile[] listFilesInDirectory(final String relPath) throws IOException { 129 FTPFile[] files; 130 131 // VFS-307: no check if we can simply list the files, this might fail if there are spaces in the path 132 files = getFtpClient().listFiles(relPath); 133 if (FTPReply.isPositiveCompletion(getFtpClient().getReplyCode())) { 134 return files; 135 } 136 137 // VFS-307: now try the hard way by cd'ing into the directory, list and cd back 138 // if VFS is required to fallback here the user might experience a real bad FTP performance 139 // as then every list requires 4 ftp commands. 140 String workingDirectory = null; 141 if (relPath != null) { 142 workingDirectory = getFtpClient().printWorkingDirectory(); 143 if (!getFtpClient().changeWorkingDirectory(relPath)) { 144 return null; 145 } 146 } 147 148 files = getFtpClient().listFiles(); 149 150 if (relPath != null && !getFtpClient().changeWorkingDirectory(workingDirectory)) { 151 throw new FileSystemException("vfs.provider.ftp.wrapper/change-work-directory-back.error", 152 workingDirectory); 153 } 154 return files; 155 } 156 157 @Override 158 public boolean removeDirectory(final String relPath) throws IOException { 159 try { 160 return getFtpClient().removeDirectory(relPath); 161 } catch (final IOException e) { 162 disconnect(); 163 return getFtpClient().removeDirectory(relPath); 164 } 165 } 166 167 @Override 168 public boolean deleteFile(final String relPath) throws IOException { 169 try { 170 return getFtpClient().deleteFile(relPath); 171 } catch (final IOException e) { 172 disconnect(); 173 return getFtpClient().deleteFile(relPath); 174 } 175 } 176 177 @Override 178 public boolean rename(final String oldName, final String newName) throws IOException { 179 try { 180 return getFtpClient().rename(oldName, newName); 181 } catch (final IOException e) { 182 disconnect(); 183 return getFtpClient().rename(oldName, newName); 184 } 185 } 186 187 @Override 188 public boolean makeDirectory(final String relPath) throws IOException { 189 try { 190 return getFtpClient().makeDirectory(relPath); 191 } catch (final IOException e) { 192 disconnect(); 193 return getFtpClient().makeDirectory(relPath); 194 } 195 } 196 197 @Override 198 public boolean completePendingCommand() throws IOException { 199 if (ftpClient != null) { 200 return getFtpClient().completePendingCommand(); 201 } 202 203 return true; 204 } 205 206 @Override 207 public InputStream retrieveFileStream(final String relPath) throws IOException { 208 try { 209 return getFtpClient().retrieveFileStream(relPath); 210 } catch (final IOException e) { 211 disconnect(); 212 return getFtpClient().retrieveFileStream(relPath); 213 } 214 } 215 216 @Override 217 public InputStream retrieveFileStream(final String relPath, final long restartOffset) throws IOException { 218 try { 219 final FTPClient client = getFtpClient(); 220 client.setRestartOffset(restartOffset); 221 return client.retrieveFileStream(relPath); 222 } catch (final IOException e) { 223 disconnect(); 224 final FTPClient client = getFtpClient(); 225 client.setRestartOffset(restartOffset); 226 return client.retrieveFileStream(relPath); 227 } 228 } 229 230 @Override 231 public OutputStream appendFileStream(final String relPath) throws IOException { 232 try { 233 return getFtpClient().appendFileStream(relPath); 234 } catch (final IOException e) { 235 disconnect(); 236 return getFtpClient().appendFileStream(relPath); 237 } 238 } 239 240 @Override 241 public OutputStream storeFileStream(final String relPath) throws IOException { 242 try { 243 return getFtpClient().storeFileStream(relPath); 244 } catch (final IOException e) { 245 disconnect(); 246 return getFtpClient().storeFileStream(relPath); 247 } 248 } 249 250 @Override 251 public boolean abort() throws IOException { 252 try { 253 // imario@apache.org: 2005-02-14 254 // it should be better to really "abort" the transfer, but 255 // currently I didnt manage to make it work - so lets "abort" the hard way. 256 // return getFtpClient().abort(); 257 258 disconnect(); 259 return true; 260 } catch (final IOException e) { 261 disconnect(); 262 } 263 return true; 264 } 265 266 @Override 267 public String getReplyString() throws IOException { 268 return getFtpClient().getReplyString(); 269 } 270}