KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > fr > jayasoft > ivy > repository > vsftp > VsftpRepository


1 package fr.jayasoft.ivy.repository.vsftp;
2
3 import java.io.File JavaDoc;
4 import java.io.IOException JavaDoc;
5 import java.io.InputStreamReader JavaDoc;
6 import java.io.PrintWriter JavaDoc;
7 import java.io.Reader JavaDoc;
8 import java.text.SimpleDateFormat JavaDoc;
9 import java.util.ArrayList JavaDoc;
10 import java.util.List JavaDoc;
11 import java.util.Locale JavaDoc;
12 import java.util.regex.Pattern JavaDoc;
13
14 import fr.jayasoft.ivy.Ivy;
15 import fr.jayasoft.ivy.IvyContext;
16 import fr.jayasoft.ivy.event.IvyEvent;
17 import fr.jayasoft.ivy.event.IvyListener;
18 import fr.jayasoft.ivy.event.resolve.EndResolveEvent;
19 import fr.jayasoft.ivy.repository.AbstractRepository;
20 import fr.jayasoft.ivy.repository.BasicResource;
21 import fr.jayasoft.ivy.repository.Resource;
22 import fr.jayasoft.ivy.repository.TransferEvent;
23 import fr.jayasoft.ivy.util.IvyThread;
24 import fr.jayasoft.ivy.util.Message;
25
26 /**
27  * Repository using SecureCRT vsftp command line program to access an sftp repository
28  *
29  * This is especially useful to leverage the gssapi authentication supported by SecureCRT.
30  *
31  * In caseswhere usual sftp is enough, prefer the 100% java solution of sftp repository.
32  *
33  * This requires SecureCRT to be in the PATH.
34  *
35  * Tested with SecureCRT 5.0.5
36  *
37  * @author Xavier Hanin
38  *
39  */

40 public class VsftpRepository extends AbstractRepository {
41     private static final String JavaDoc PROMPT = "vsftp> ";
42
43     private static final SimpleDateFormat JavaDoc FORMAT = new SimpleDateFormat JavaDoc("MMM dd, yyyy HH:mm", Locale.US);
44     
45     private String JavaDoc _host;
46     private String JavaDoc _username;
47     private String JavaDoc _authentication = "gssapi";
48     
49     private Reader JavaDoc _in;
50     private Reader JavaDoc _err;
51     private PrintWriter JavaDoc _out;
52     
53     private volatile StringBuffer JavaDoc _errors = new StringBuffer JavaDoc();
54
55     private long _readTimeout = 30000;
56     
57     private long _reuseConnection = 5 * 60 * 1000; // reuse connection during 5 minutes by default
58

59     private volatile long _lastCommand;
60
61     private volatile boolean _inCommand;
62
63     private Process JavaDoc _process;
64
65     private Thread JavaDoc _connectionCleaner;
66
67     private Thread JavaDoc _errorsReader;
68     private volatile long _errorsLastUpdateTime;
69     
70     private Ivy _ivy = null;
71
72     public Resource getResource(String JavaDoc source) throws IOException JavaDoc {
73         initIvy();
74         return new VsftpResource(this, source);
75     }
76
77     private void initIvy() {
78         _ivy = IvyContext.getContext().getIvy();
79     }
80
81     protected Resource getInitResource(String JavaDoc source) throws IOException JavaDoc {
82         try {
83             return lslToResource(source, sendCommand("ls -l "+source, true, true));
84         } catch (IOException JavaDoc ex) {
85             cleanup(ex);
86             throw ex;
87         } finally {
88             cleanup();
89         }
90     }
91
92     public void get(final String JavaDoc source, File JavaDoc destination) throws IOException JavaDoc {
93         initIvy();
94         try {
95             fireTransferInitiated(getResource(source), TransferEvent.REQUEST_GET);
96             File JavaDoc destDir = destination.getParentFile();
97             if (destDir != null) {
98                 sendCommand("lcd "+destDir.getAbsolutePath());
99             }
100             if (destination.exists()) {
101                 destination.delete();
102             }
103
104             int index = source.lastIndexOf('/');
105             String JavaDoc srcName = index == -1?source:source.substring(index+1);
106             final File JavaDoc to = destDir == null ? new File JavaDoc(srcName):new File JavaDoc(destDir, srcName);
107
108             final IOException JavaDoc ex[] = new IOException JavaDoc[1];
109             Thread JavaDoc get = new IvyThread() {
110                 public void run() {
111                     initContext();
112                     try {
113                         sendCommand("get "+source, getExpectedDownloadMessage(source, to), 0);
114                     } catch (IOException JavaDoc e) {
115                         ex[0] = e;
116                     }
117                 }
118             };
119             get.start();
120             
121             long prevLength = 0;
122             long lastUpdate = System.currentTimeMillis();
123             long timeout = _readTimeout;
124             while (get.isAlive()) {
125                 checkInterrupted();
126                 long length = to.exists()?to.length():0;
127                 if (length > prevLength) {
128                     fireTransferProgress(length - prevLength);
129                     lastUpdate = System.currentTimeMillis();
130                     prevLength = length;
131                 } else {
132                     if (System.currentTimeMillis() - lastUpdate > timeout) {
133                         Message.verbose("download hang for more than "+timeout+"ms. Interrupting.");
134                         get.interrupt();
135                         if (to.exists()) to.delete();
136                         throw new IOException JavaDoc(source+" download timeout from "+getHost());
137                     }
138                 }
139                 try {
140                     get.join(100);
141                 } catch (InterruptedException JavaDoc e) {
142                     if (to.exists()) to.delete();
143                     return;
144                 }
145             }
146             if (ex[0] != null) {
147                 if (to.exists()) to.delete();
148                 throw ex[0];
149             }
150
151             to.renameTo(destination);
152             fireTransferCompleted(destination.length());
153         } catch (IOException JavaDoc ex) {
154             fireTransferError(ex);
155             cleanup(ex);
156             throw ex;
157         } finally {
158             cleanup();
159         }
160     }
161
162     public List JavaDoc list(String JavaDoc parent) throws IOException JavaDoc {
163         initIvy();
164         try {
165             if (!parent.endsWith("/")) {
166                 parent = parent+"/";
167             }
168             String JavaDoc response = sendCommand("ls -l "+parent, true, true);
169             if (response.startsWith("ls")) {
170                 return null;
171             }
172             String JavaDoc[] lines = response.split("\n");
173             List JavaDoc ret = new ArrayList JavaDoc(lines.length);
174             for (int i = 0; i < lines.length; i++) {
175                 while (lines[i].endsWith("\r") || lines[i].endsWith("\n")) {
176                     lines[i] = lines[i].substring(0, lines[i].length() -1);
177                 }
178                 if (lines[i].trim().length() != 0) {
179                     ret.add(parent+lines[i].substring(lines[i].lastIndexOf(' ')+1));
180                 }
181             }
182             return ret;
183         } catch (IOException JavaDoc ex) {
184             cleanup(ex);
185             throw ex;
186         } finally {
187             cleanup();
188         }
189     }
190
191     public void put(File JavaDoc source, String JavaDoc destination, boolean overwrite) throws IOException JavaDoc {
192         initIvy();
193         try {
194             if (getResource(destination).exists()) {
195                 if (overwrite) {
196                     sendCommand("rm "+destination, getExpectedRemoveMessage(destination));
197                 } else {
198                     return;
199                 }
200             }
201             int index = destination.lastIndexOf('/');
202             String JavaDoc destDir = null;
203             if (index != -1) {
204                 destDir = destination.substring(0, index);
205                 mkdirs(destDir);
206                 sendCommand("cd "+destDir);
207             }
208             String JavaDoc to = destDir != null ? destDir+"/"+source.getName():source.getName();
209             sendCommand("put "+source.getAbsolutePath(), getExpectedUploadMessage(source, to), 0);
210             sendCommand("mv "+to+" "+destination);
211         } catch (IOException JavaDoc ex) {
212             cleanup(ex);
213             throw ex;
214         } finally {
215             cleanup();
216         }
217     }
218
219
220     private void mkdirs(String JavaDoc destDir) throws IOException JavaDoc {
221         if (dirExists(destDir)) {
222             return;
223         }
224         if (destDir.endsWith("/")) {
225             destDir = destDir.substring(0, destDir.length() - 1);
226         }
227         int index = destDir.lastIndexOf('/');
228         if (index != - 1) {
229             mkdirs(destDir.substring(0, index));;
230         }
231         sendCommand("mkdir "+destDir);
232     }
233
234     private boolean dirExists(String JavaDoc dir) throws IOException JavaDoc {
235         return !sendCommand("ls "+dir, true).startsWith("ls: ");
236     }
237
238     protected String JavaDoc sendCommand(String JavaDoc command) throws IOException JavaDoc {
239         return sendCommand(command, false, _readTimeout);
240     }
241     
242     protected void sendCommand(String JavaDoc command, Pattern JavaDoc expectedResponse) throws IOException JavaDoc {
243         sendCommand(command, expectedResponse, _readTimeout);
244     }
245
246     /**
247      * The behaviour of vsftp with some commands is to log the resulting message on the error stream,
248      * even if everything is ok.
249      *
250      * So it's quite difficult if there was an error or not.
251      *
252      * Hence we compare the response with the expected message and deal with it.
253      * The problem is that this is very specific to the version of vsftp used for the test,
254      *
255      * That's why expected messages are obtained using overridable protected methods.
256      */

257     protected void sendCommand(String JavaDoc command, Pattern JavaDoc expectedResponse, long timeout) throws IOException JavaDoc {
258         String JavaDoc response = sendCommand(command, true, timeout);
259         if (!expectedResponse.matcher(response).matches()) {
260             Message.debug("invalid response from server:");
261             Message.debug("expected: '"+expectedResponse+"'");
262             Message.debug("was: '"+response+"'");
263             throw new IOException JavaDoc(response);
264         }
265     }
266     protected String JavaDoc sendCommand(String JavaDoc command, boolean sendErrorAsResponse) throws IOException JavaDoc {
267         return sendCommand(command, sendErrorAsResponse, _readTimeout);
268     }
269
270     protected String JavaDoc sendCommand(String JavaDoc command, boolean sendErrorAsResponse, boolean single) throws IOException JavaDoc {
271         return sendCommand(command, sendErrorAsResponse, single, _readTimeout);
272     }
273
274     protected String JavaDoc sendCommand(String JavaDoc command, boolean sendErrorAsResponse, long timeout) throws IOException JavaDoc {
275         return sendCommand(command, sendErrorAsResponse, false, timeout);
276     }
277     
278     protected String JavaDoc sendCommand(String JavaDoc command, boolean sendErrorAsResponse, boolean single, long timeout) throws IOException JavaDoc {
279         single = false; // use of alone commands does not work properly due to a long delay between end of process and end of stream...
280

281         checkInterrupted();
282         _inCommand = true;
283         _errorsLastUpdateTime = 0;
284         synchronized (this) {
285             if (!single || _in != null) {
286                 ensureConnectionOpened();
287                 Message.debug("sending command '"+command+"' to "+getHost());
288                 updateLastCommandTime();
289                 _out.println(command);
290                 _out.flush();
291             } else {
292                 sendSingleCommand(command);
293             }
294         }
295         
296         try {
297             return readResponse(sendErrorAsResponse, timeout);
298         } finally {
299             _inCommand = false;
300             if (single) {
301                 closeConnection();
302             }
303         }
304     }
305
306     protected String JavaDoc readResponse(boolean sendErrorAsResponse) throws IOException JavaDoc {
307         return readResponse(sendErrorAsResponse, _readTimeout);
308     }
309
310     protected synchronized String JavaDoc readResponse(final boolean sendErrorAsResponse, long timeout) throws IOException JavaDoc {
311         final StringBuffer JavaDoc response = new StringBuffer JavaDoc();
312         final IOException JavaDoc[] exc = new IOException JavaDoc[1];
313         final boolean[] done = new boolean[1];
314         Runnable JavaDoc r = new Runnable JavaDoc() {
315             public void run() {
316                 synchronized (VsftpRepository.this) {
317                     try {
318                         int c;
319                         boolean getPrompt = false;
320                         // the reading is done in a for loop making five attempts to read the stream if we do not reach the next prompt
321
for (int attempts = 0; !getPrompt && attempts < 5; attempts++) {
322                             while ((c = _in.read()) != -1) {
323                                 attempts = 0; // we manage to read something, reset numer of attempts
324
response.append((char)c);
325                                 if (response.length() >= PROMPT.length()
326                                         && response.substring(response.length() - PROMPT.length(), response.length()).equals(PROMPT)) {
327                                     response.setLength(response.length() - PROMPT.length());
328                                     getPrompt = true;
329                                     break;
330                                 }
331                             }
332                             if (!getPrompt) {
333                                 try {
334                                     Thread.sleep(50);
335                                 } catch (InterruptedException JavaDoc e) {
336                                     break;
337                                 }
338                             }
339                         }
340                         if (getPrompt) {
341                             // wait enough for error stream to be fully read
342
if (_errorsLastUpdateTime == 0) {
343                                 // no error written yet, but it may be pending...
344
_errorsLastUpdateTime = _lastCommand;
345                             }
346                                 
347                             while ((System.currentTimeMillis() - _errorsLastUpdateTime) < 50) {
348                                 try {
349                                     Thread.sleep(30);
350                                 } catch (InterruptedException JavaDoc e) {
351                                     break;
352                                 }
353                             }
354                         }
355                         if (_errors.length() > 0) {
356                             if (sendErrorAsResponse) {
357                                 response.append(_errors);
358                                 _errors.setLength(0);
359                             } else {
360                                 throw new IOException JavaDoc(chomp(_errors).toString());
361                             }
362                         }
363                         chomp(response);
364                         done[0] = true;
365                     } catch (IOException JavaDoc e) {
366                         exc[0] = e;
367                     } finally {
368                         VsftpRepository.this.notify();
369                     }
370                 }
371             }
372         };
373         Thread JavaDoc reader = null;
374         if (timeout == 0) {
375             r.run();
376         } else {
377             reader = new IvyThread(r);
378             reader.start();
379             try {
380                 wait(timeout);
381             } catch (InterruptedException JavaDoc e) {
382             }
383         }
384         updateLastCommandTime();
385         if (exc[0] != null) {
386             throw exc[0];
387         } else if (!done[0]) {
388             if (reader != null && reader.isAlive()) {
389                 reader.interrupt();
390                 for (int i = 0; i<5 && reader.isAlive(); i++) {
391                     try {
392                         Thread.sleep(100);
393                     } catch (InterruptedException JavaDoc e) {
394                         break;
395                     }
396                 }
397                 if (reader.isAlive()) {
398                     reader.stop(); // no way to interrupt it non abruptly
399
}
400             }
401             throw new IOException JavaDoc("connection timeout to "+getHost());
402         } else {
403             if ("Not connected.".equals(response)) {
404                 Message.info("vsftp connection to "+getHost()+" reset");
405                 closeConnection();
406                 throw new IOException JavaDoc("not connected to "+getHost());
407             }
408             Message.debug("received response '"+response+"' from "+getHost());
409             return response.toString();
410         }
411     }
412
413     private synchronized void sendSingleCommand(String JavaDoc command) throws IOException JavaDoc {
414         exec(getSingleCommand(command));
415     }
416
417     protected synchronized void ensureConnectionOpened() throws IOException JavaDoc {
418         if (_in == null) {
419             Message.verbose("connecting to "+getUsername()+"@"+getHost()+"... ");
420             String JavaDoc connectionCommand = getConnectionCommand();
421             exec(connectionCommand);
422
423             try {
424                 readResponse(false); // waits for first prompt
425

426                 if (_reuseConnection > 0) {
427                     _connectionCleaner = new IvyThread() {
428                         public void run() {
429                             initContext();
430                             try {
431                                 long sleep = 10;
432                                 while (_in != null && sleep > 0) {
433                                     sleep(sleep);
434                                     sleep = _reuseConnection - (System.currentTimeMillis() - _lastCommand);
435                                     if (_inCommand) {
436                                         sleep = sleep <= 0 ? _reuseConnection : sleep;
437                                     }
438                                 }
439                             } catch (InterruptedException JavaDoc e) {
440                             }
441                             disconnect();
442                         }
443                     };
444                     _connectionCleaner.start();
445                 }
446
447                 if (_ivy != null) {
448                     _ivy.addIvyListener(new IvyListener() {
449                         public void progress(IvyEvent event) {
450                             disconnect();
451                             event.getSource().removeIvyListener(this);
452                         }
453                     }, EndResolveEvent.NAME);
454                 }
455                 
456                 
457             } catch (IOException JavaDoc ex) {
458                 closeConnection();
459                 throw new IOException JavaDoc("impossible to connect to "+getUsername()+"@"+getHost()+" using "+getAuthentication()+": "+ex.getMessage());
460             }
461             Message.verbose("connected to "+getHost());
462         }
463     }
464
465     private void updateLastCommandTime() {
466         _lastCommand = System.currentTimeMillis();
467     }
468
469     private void exec(String JavaDoc command) throws IOException JavaDoc {
470         Message.debug("launching '"+command+"'");
471         _process = Runtime.getRuntime().exec(command);
472         _in = new InputStreamReader JavaDoc(_process.getInputStream());
473         _err = new InputStreamReader JavaDoc(_process.getErrorStream());
474         _out = new PrintWriter JavaDoc(_process.getOutputStream());
475
476         _errorsReader = new IvyThread() {
477                             public void run() {
478                                 initContext();
479                                 int c;
480                                 try {
481                                     while (_err != null && (c = _err.read()) != -1) {
482                                         _errors.append((char)c);
483                                         _errorsLastUpdateTime = System.currentTimeMillis();
484                                     }
485                                 } catch (IOException JavaDoc e) {
486                                 }
487                             }
488                         };
489         _errorsReader.start();
490     }
491
492
493     private void checkInterrupted() {
494         if (_ivy != null) {
495             _ivy.checkInterrupted();
496         }
497     }
498
499     /**
500      * Called whenever an api level method end
501      */

502     private void cleanup(Exception JavaDoc ex) {
503         if (ex.getMessage().equals("connection timeout to "+getHost())) {
504             closeConnection();
505         } else {
506             disconnect();
507         }
508     }
509
510     /**
511      * Called whenever an api level method end
512      */

513     private void cleanup() {
514         if (_reuseConnection == 0) {
515             disconnect();
516         }
517     }
518     
519     public synchronized void disconnect() {
520         if (_in != null) {
521             Message.verbose("disconnecting from "+getHost()+"... ");
522             try {
523                 sendCommand("exit", false, 300);
524             } catch (IOException JavaDoc e) {
525             } finally {
526                 closeConnection();
527                 Message.verbose("disconnected of "+getHost());
528             }
529         }
530     }
531
532     private synchronized void closeConnection() {
533         if (_connectionCleaner != null) {
534             _connectionCleaner.interrupt();
535         }
536         if (_errorsReader != null) {
537             _errorsReader.interrupt();
538         }
539         try {
540             _process.destroy();
541         } catch (Exception JavaDoc ex) {}
542         try {
543             _in.close();
544         } catch (Exception JavaDoc e) {}
545         try {
546             _err.close();
547         } catch (Exception JavaDoc e) {}
548         try {
549             _out.close();
550         } catch (Exception JavaDoc e) {}
551         
552         _connectionCleaner = null;
553         _errorsReader = null;
554         _process = null;
555         _in = null;
556         _out = null;
557         _err = null;
558         Message.debug("connection to "+getHost()+" closed");
559     }
560
561     /**
562      * Parses a ls -l line and transforms it in a resource
563      * @param file
564      * @param responseLine
565      * @return
566      */

567     protected Resource lslToResource(String JavaDoc file, String JavaDoc responseLine) {
568         if (responseLine == null || responseLine.startsWith("ls")) {
569             return new BasicResource(file, false, 0, 0, false);
570         } else {
571             String JavaDoc[] parts = responseLine.split("\\s+");
572             if (parts.length != 9) {
573                 Message.debug("unrecognized ls format: "+responseLine);
574                 return new BasicResource(file, false, 0, 0, false);
575             } else {
576                 try {
577                     long contentLength = Long.parseLong(parts[3]);
578                     String JavaDoc date = parts[4]+" "+parts[5]+" "+parts[6]+" "+parts[7];
579                     return new BasicResource(file, true, contentLength, FORMAT.parse(date).getTime(), false);
580                 } catch (Exception JavaDoc ex) {
581                     Message.warn("impossible to parse server response: "+responseLine+": "+ex);
582                     return new BasicResource(file, false, 0, 0, false);
583                 }
584             }
585         }
586     }
587
588     protected String JavaDoc getSingleCommand(String JavaDoc command) {
589         return "vsh -noprompt -auth "+_authentication+" "+_username+"@"+_host+" "+command;
590     }
591     
592     protected String JavaDoc getConnectionCommand() {
593         return "vsftp -noprompt -auth "+_authentication+" "+_username+"@"+_host;
594     }
595     
596     protected Pattern JavaDoc getExpectedDownloadMessage(String JavaDoc source, File JavaDoc to) {
597         return Pattern.compile("Downloading "+to.getName()+" from [^\\s]+");
598     }
599
600     protected Pattern JavaDoc getExpectedRemoveMessage(String JavaDoc destination) {
601         return Pattern.compile("Removing [^\\s]+");
602     }
603
604     protected Pattern JavaDoc getExpectedUploadMessage(File JavaDoc source, String JavaDoc to) {
605         return Pattern.compile("Uploading "+source.getName()+" to [^\\s]+");
606     }
607
608
609     public String JavaDoc getAuthentication() {
610         return _authentication;
611     }
612
613     public void setAuthentication(String JavaDoc authentication) {
614         _authentication = authentication;
615     }
616
617     public String JavaDoc getHost() {
618         return _host;
619     }
620
621     public void setHost(String JavaDoc host) {
622         _host = host;
623     }
624
625     public String JavaDoc getUsername() {
626         return _username;
627     }
628
629     public void setUsername(String JavaDoc username) {
630         _username = username;
631     }
632
633     private static StringBuffer JavaDoc chomp(StringBuffer JavaDoc str) {
634         if (str == null || str.length() == 0) {
635             return str;
636         }
637         while ("\n".equals(str.substring(str.length() - 1)) || "\r".equals(str.substring(str.length() - 1))) {
638             str.setLength(str.length() - 1);
639         }
640         return str;
641     }
642
643     public String JavaDoc toString() {
644         return getName()+" "+getUsername()+"@"+getHost()+" ("+getAuthentication()+")";
645     }
646
647     /**
648      * Sets the reuse connection time.
649      * The same connection will be reused if the time here does not last
650      * between two commands.
651      * O indicates that the connection should never be reused
652      *
653      * @param time
654      */

655     public void setReuseConnection(long time) {
656         _reuseConnection = time;
657     }
658
659     public long getReadTimeout() {
660         return _readTimeout;
661     }
662
663     public void setReadTimeout(long readTimeout) {
664         _readTimeout = readTimeout;
665     }
666 }
667
Popular Tags