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