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 @Override 066 public Resource getResource(String path) { 067 path=ResourceUtil.removeScheme(scheme,path); 068 FTPConnectionData data=new FTPConnectionData(); 069 path=data.load(path); 070 071 return new FTPResource(this,data,path); 072 } 073 074 075 076 077 FTPResourceClient getClient(FTPConnectionData data) throws IOException { 078 079 FTPResourceClient client=(FTPResourceClient) clients.remove(data.key()); 080 if(client==null) { 081 client = new FTPResourceClient(data,cache); 082 if(socketTimeout>0)client.setSoTimeout(socketTimeout); 083 } 084 085 if(!client.isConnected()) { 086 if(data.hasProxyData()) { 087 try { 088 Proxy.start( 089 data.getProxyserver(), 090 data.getProxyport(), 091 data.getProxyuser(), 092 data.getProxypassword() 093 ); 094 connect(client,data); 095 } 096 finally { 097 Proxy.end(); 098 } 099 } 100 else { 101 connect(client,data); 102 } 103 104 int replyCode = client.getReplyCode(); 105 if(replyCode>=400) 106 throw new FTPException(replyCode); 107 } 108 startCloser(); 109 return client; 110 } 111 112 private synchronized void startCloser() { 113 if(closer==null || !closer.isAlive()) { 114 closer=new FTPResourceClientCloser(this); 115 closer.start(); 116 } 117 118 } 119 private void connect(FTPResourceClient client, FTPConnectionData data) throws SocketException, IOException { 120 if(data.port>0)client.connect(data.host,data.port); 121 else client.connect(data.host); 122 if(!StringUtil.isEmpty(data.username))client.login(data.username,data.password); 123 } 124 125 public void returnClient(FTPResourceClient client) { 126 if(client==null)return; 127 client.touch(); 128 clients.put(client.getFtpConnectionData().key(), client); 129 } 130 131 @Override 132 public String getScheme() { 133 return scheme; 134 } 135 136 public void setScheme(String scheme) { 137 if(!StringUtil.isEmpty(scheme))this.scheme=scheme; 138 } 139 140 @Override 141 public void setResources(Resources resources) { 142 //this.resources=resources; 143 } 144 145 @Override 146 public void lock(Resource res) throws IOException { 147 lock.lock(res); 148 } 149 150 @Override 151 public void unlock(Resource res) { 152 lock.unlock(res); 153 } 154 155 @Override 156 public void read(Resource res) throws IOException { 157 lock.read(res); 158 } 159 160 public void clean() { 161 Object[] keys = clients.keySet().toArray(); 162 FTPResourceClient client; 163 for(int i=0;i<keys.length;i++) { 164 client=(FTPResourceClient) clients.get(keys[i]); 165 if(client.getLastAccess()+clientTimeout<System.currentTimeMillis()) { 166 //railo.print.ln("disconnect:"+client.getFtpConnectionData().key()); 167 if(client.isConnected()) { 168 try { 169 client.disconnect(); 170 } 171 catch (IOException e) {} 172 } 173 clients.remove(client.getFtpConnectionData().key()); 174 } 175 } 176 } 177 178 class FTPResourceClientCloser extends Thread { 179 180 private FTPResourceProvider provider; 181 182 public FTPResourceClientCloser(FTPResourceProvider provider) { 183 this.provider=provider; 184 } 185 186 public void run() { 187 //railo.print.ln("closer start"); 188 do { 189 sleepEL(); 190 provider.clean(); 191 } 192 while(!clients.isEmpty()); 193 //railo.print.ln("closer stop"); 194 } 195 196 private void sleepEL() { 197 try { 198 sleep(provider.clientTimeout); 199 } catch (InterruptedException e) {} 200 } 201 } 202 203 /** 204 * @return the cache 205 */ 206 public int getCache() { 207 return cache; 208 } 209 210 @Override 211 public boolean isAttributesSupported() { 212 return false; 213 } 214 215 public boolean isCaseSensitive() { 216 return true; 217 } 218 219 @Override 220 public boolean isModeSupported() { 221 return true; 222 } 223 224 @Override 225 public long sizeOf() { 226 return SizeOf.size(lock)+SizeOf.size(clients); 227 } 228 229 @Override 230 public Map getArguments() { 231 return arguments; 232 } 233 234 } 235