001 package railo.commons.io.res.type.ftp; 002 003 004 005 import java.io.IOException; 006 import java.net.SocketException; 007 import java.util.HashMap; 008 import java.util.Map; 009 010 import railo.commons.io.res.Resource; 011 import railo.commons.io.res.ResourceProvider; 012 import railo.commons.io.res.Resources; 013 import railo.commons.io.res.util.ResourceLockImpl; 014 import railo.commons.io.res.util.ResourceUtil; 015 import railo.commons.lang.SizeOf; 016 import railo.commons.lang.StringUtil; 017 import railo.runtime.net.proxy.Proxy; 018 import railo.runtime.op.Caster; 019 import railo.runtime.type.Sizeable; 020 021 // TODO check connection timeout 022 public final class FTPResourceProvider implements ResourceProvider,Sizeable { 023 private String scheme="ftp"; 024 private final Map clients=new HashMap(); 025 private int clientTimeout=60000; 026 private int socketTimeout=-1; 027 private int lockTimeout=20000; 028 private int cache=20000; 029 030 private FTPResourceClientCloser closer=null; 031 private final ResourceLockImpl lock=new ResourceLockImpl(lockTimeout,true); 032 private Map arguments; 033 034 public ResourceProvider init(String scheme, Map arguments) { 035 setScheme(scheme); 036 037 if(arguments!=null) { 038 this.arguments=arguments; 039 // client-timeout 040 String strTimeout=(String) arguments.get("client-timeout"); 041 if(strTimeout!=null) { 042 clientTimeout=Caster.toIntValue(strTimeout,clientTimeout); 043 } 044 // socket-timeout 045 strTimeout=(String) arguments.get("socket-timeout"); 046 if(strTimeout!=null) { 047 socketTimeout=Caster.toIntValue(strTimeout,socketTimeout); 048 } 049 // lock-timeout 050 strTimeout=(String) arguments.get("lock-timeout"); 051 if(strTimeout!=null) { 052 lockTimeout=Caster.toIntValue(strTimeout,lockTimeout); 053 } 054 // cache 055 String strCache=(String) arguments.get("cache"); 056 if(strCache!=null) { 057 cache=Caster.toIntValue(strCache,cache); 058 } 059 } 060 lock.setLockTimeout(lockTimeout); 061 062 return this; 063 } 064 065 /** 066 * path must have the following format:<BR> 067 * ftp://[ username[: password]@] hostname[: port][ absolute-path] 068 * @see res.ResourceProvider#getResource(java.lang.String) 069 */ 070 public Resource getResource(String path) { 071 path=ResourceUtil.removeScheme(scheme,path); 072 FTPConnectionData data=new FTPConnectionData(); 073 path=data.load(path); 074 075 return new FTPResource(this,data,path); 076 } 077 078 079 080 081 FTPResourceClient getClient(FTPConnectionData data) throws IOException { 082 083 FTPResourceClient client=(FTPResourceClient) clients.remove(data.key()); 084 if(client==null) { 085 client = new FTPResourceClient(data,cache); 086 if(socketTimeout>0)client.setSoTimeout(socketTimeout); 087 } 088 089 if(!client.isConnected()) { 090 if(data.hasProxyData()) { 091 try { 092 Proxy.start( 093 data.getProxyserver(), 094 data.getProxyport(), 095 data.getProxyuser(), 096 data.getProxypassword() 097 ); 098 connect(client,data); 099 } 100 finally { 101 Proxy.end(); 102 } 103 } 104 else { 105 connect(client,data); 106 } 107 108 int replyCode = client.getReplyCode(); 109 if(replyCode>=400) 110 throw new FTPException(replyCode); 111 } 112 startCloser(); 113 return client; 114 } 115 116 private synchronized void startCloser() { 117 if(closer==null || !closer.isAlive()) { 118 closer=new FTPResourceClientCloser(this); 119 closer.start(); 120 } 121 122 } 123 private void connect(FTPResourceClient client, FTPConnectionData data) throws SocketException, IOException { 124 if(data.port>0)client.connect(data.host,data.port); 125 else client.connect(data.host); 126 if(!StringUtil.isEmpty(data.username))client.login(data.username,data.password); 127 } 128 129 public void returnClient(FTPResourceClient client) { 130 if(client==null)return; 131 client.touch(); 132 clients.put(client.getFtpConnectionData().key(), client); 133 } 134 135 /** 136 * @see res.ResourceProvider#getScheme() 137 */ 138 public String getScheme() { 139 return scheme; 140 } 141 142 /** 143 * 144 * @see railo.commons.io.res.ResourceProvider#setScheme(java.lang.String) 145 */ 146 public void setScheme(String scheme) { 147 if(!StringUtil.isEmpty(scheme))this.scheme=scheme; 148 } 149 150 /** 151 * @see railo.commons.io.res.ResourceProvider#setResources(railo.commons.io.res.Resources) 152 */ 153 public void setResources(Resources resources) { 154 //this.resources=resources; 155 } 156 157 /** 158 * @throws IOException 159 * @see railo.commons.io.res.ResourceProvider#lock(railo.commons.io.res.Resource) 160 */ 161 public void lock(Resource res) throws IOException { 162 lock.lock(res); 163 } 164 165 /** 166 * @see railo.commons.io.res.ResourceProvider#unlock(railo.commons.io.res.Resource) 167 */ 168 public void unlock(Resource res) { 169 lock.unlock(res); 170 } 171 172 /** 173 * @throws IOException 174 * @see railo.commons.io.res.ResourceProvider#read(railo.commons.io.res.Resource) 175 */ 176 public void read(Resource res) throws IOException { 177 lock.read(res); 178 } 179 180 public void clean() { 181 Object[] keys = clients.keySet().toArray(); 182 FTPResourceClient client; 183 for(int i=0;i<keys.length;i++) { 184 client=(FTPResourceClient) clients.get(keys[i]); 185 if(client.getLastAccess()+clientTimeout<System.currentTimeMillis()) { 186 //railo.print.ln("disconnect:"+client.getFtpConnectionData().key()); 187 if(client.isConnected()) { 188 try { 189 client.disconnect(); 190 } 191 catch (IOException e) {} 192 } 193 clients.remove(client.getFtpConnectionData().key()); 194 } 195 } 196 } 197 198 class FTPResourceClientCloser extends Thread { 199 200 private FTPResourceProvider provider; 201 202 public FTPResourceClientCloser(FTPResourceProvider provider) { 203 this.provider=provider; 204 } 205 206 public void run() { 207 //railo.print.ln("closer start"); 208 do { 209 sleepEL(); 210 provider.clean(); 211 } 212 while(!clients.isEmpty()); 213 //railo.print.ln("closer stop"); 214 } 215 216 private void sleepEL() { 217 try { 218 sleep(provider.clientTimeout); 219 } catch (InterruptedException e) {} 220 } 221 } 222 223 /** 224 * @return the cache 225 */ 226 public int getCache() { 227 return cache; 228 } 229 230 /** 231 * @see railo.commons.io.res.ResourceProvider#isAttributesSupported() 232 */ 233 public boolean isAttributesSupported() { 234 return false; 235 } 236 237 public boolean isCaseSensitive() { 238 return true; 239 } 240 241 /** 242 * 243 * @see railo.commons.io.res.ResourceProvider#isModeSupported() 244 */ 245 public boolean isModeSupported() { 246 return true; 247 } 248 249 /** 250 * @see railo.runtime.type.Sizeable#sizeOf() 251 */ 252 public long sizeOf() { 253 return SizeOf.size(lock)+SizeOf.size(clients); 254 } 255 256 /** 257 * @see railo.commons.io.res.ResourceProvider#getArguments() 258 */ 259 public Map getArguments() { 260 return arguments; 261 } 262 263 } 264