KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > hudson > FilePath


1 package hudson;
2
3 import hudson.remoting.Callable;
4 import hudson.remoting.Channel;
5 import hudson.remoting.Pipe;
6 import hudson.remoting.RemoteOutputStream;
7 import hudson.remoting.VirtualChannel;
8 import hudson.remoting.DelegatingCallable;
9 import hudson.util.IOException2;
10 import hudson.model.Hudson;
11 import org.apache.tools.ant.BuildException;
12 import org.apache.tools.ant.DirectoryScanner;
13 import org.apache.tools.ant.taskdefs.Copy;
14 import org.apache.tools.ant.types.FileSet;
15
16 import java.io.File JavaDoc;
17 import java.io.FileFilter JavaDoc;
18 import java.io.FileInputStream JavaDoc;
19 import java.io.FileOutputStream JavaDoc;
20 import java.io.FileWriter JavaDoc;
21 import java.io.IOException JavaDoc;
22 import java.io.InputStream JavaDoc;
23 import java.io.ObjectInputStream JavaDoc;
24 import java.io.ObjectOutputStream JavaDoc;
25 import java.io.OutputStream JavaDoc;
26 import java.io.Serializable JavaDoc;
27 import java.io.Writer JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import java.util.List JavaDoc;
30 import java.net.URI JavaDoc;
31
32 /**
33  * {@link File} like object with remoting support.
34  *
35  * <p>
36  * Unlike {@link File}, which always implies a file path on the current computer,
37  * {@link FilePath} represents a file path on a specific slave or the master.
38  *
39  * Despite that, {@link FilePath} can be used much like {@link File}. It exposes
40  * a bunch of operations (and we should add more operations as long as they are
41  * generally useful), and when invoked against a file on a remote node, {@link FilePath}
42  * executes the necessary code remotely, thereby providing semi-transparent file
43  * operations.
44  *
45  * <h2>Using {@link FilePath} smartly</h2>
46  * <p>
47  * The transparency makes it easy to write plugins without worrying too much about
48  * remoting, by making it works like NFS, where remoting happens at the file-system
49  * later.
50  *
51  * <p>
52  * But one should note that such use of remoting may not be optional. Sometimes,
53  * it makes more sense to move some computation closer to the data, as opposed to
54  * move the data to the computation. For example, if you are just computing a MD5
55  * digest of a file, then it would make sense to do the digest on the host where
56  * the file is located, as opposed to send the whole data to the master and do MD5
57  * digesting there.
58  *
59  * <p>
60  * {@link FilePath} supports this "code migration" by in the
61  * {@link #act(FileCallable)} method. One can pass in a custom implementation
62  * of {@link FileCallable}, to be executed on the node where the data is located.
63  * The following code shows the example:
64  *
65  * <pre>
66  * FilePath file = ...;
67  *
68  * // make 'file' a fresh empty directory.
69  * file.act(new FileCallable&lt;Void>() {
70  * // if 'file' is on a different node, this FileCallable will
71  * // be transfered to that node and executed there.
72  * public Void invoke(File f,VirtualChannel channel) {
73  * // f and file represents the same thing
74  * f.deleteContents();
75  * f.mkdirs();
76  * }
77  * });
78  * </pre>
79  *
80  * <p>
81  * When {@link FileCallable} is transfered to a remote node, it will be done so
82  * by using the same Java serializaiton scheme that the remoting module uses.
83  * See {@link Channel} for more about this.
84  *
85  * <p>
86  * {@link FilePath} itself can be sent over to a remote node as a part of {@link Callable}
87  * serialization. For example, sending a {@link FilePath} of a remote node to that
88  * node causes {@link FilePath} to become "local". Similarly, sending a
89  * {@link FilePath} that represents the local computer causes it to become "remote."
90  *
91  * @author Kohsuke Kawaguchi
92  */

93 public final class FilePath implements Serializable JavaDoc {
94     /**
95      * When this {@link FilePath} represents the remote path,
96      * this field is always non-null on master (the field represents
97      * the channel to the remote slave.) When transferred to a slave via remoting,
98      * this field reverts back to null, since it's transient.
99      *
100      * When this {@link FilePath} represents a path on the master,
101      * this field is null on master. When transferred to a slave via remoting,
102      * this field becomes non-null, representing the {@link Channel}
103      * back to the master.
104      *
105      * This is used to determine whether we are running on the master or the slave.
106      */

107     private transient VirtualChannel channel;
108
109     // since the platform of the slave might be different, can't use java.io.File
110
private final String JavaDoc remote;
111
112     public FilePath(VirtualChannel channel, String JavaDoc remote) {
113         this.channel = channel;
114         this.remote = remote;
115     }
116
117     /**
118      * To create {@link FilePath} on the master computer.
119      */

120     public FilePath(File JavaDoc localPath) {
121         this.channel = null;
122         this.remote = localPath.getPath();
123     }
124
125     public FilePath(FilePath base, String JavaDoc rel) {
126         this.channel = base.channel;
127         if(base.isUnix()) {
128             this.remote = base.remote+'/'+rel;
129         } else {
130             this.remote = base.remote+'\\'+rel;
131         }
132     }
133
134     /**
135      * Checks if the remote path is Unix.
136      */

137     private boolean isUnix() {
138         // Windows can handle '/' as a path separator but Unix can't,
139
// so err on Unix side
140
return remote.indexOf("\\")==-1;
141     }
142
143     public String JavaDoc getRemote() {
144         return remote;
145     }
146
147     /**
148      * Code that gets executed on the machine where the {@link FilePath} is local.
149      * Used to act on {@link FilePath}.
150      *
151      * @see FilePath#act(FileCallable)
152      */

153     public static interface FileCallable<T> extends Serializable JavaDoc {
154         /**
155          * Performs the computational task on the node where the data is located.
156          *
157          * @param f
158          * {@link File} that represents the local file that {@link FilePath} has represented.
159          * @param channel
160          * The "back pointer" of the {@link Channel} that represents the communication
161          * with the node from where the code was sent.
162          */

163         T invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc;
164     }
165
166     /**
167      * Executes some program on the machine that this {@link FilePath} exists,
168      * so that one can perform local file operations.
169      */

170     public <T> T act(final FileCallable<T> callable) throws IOException JavaDoc, InterruptedException JavaDoc {
171         if(channel!=null) {
172             // run this on a remote system
173
try {
174                 return channel.call(new DelegatingCallable<T,IOException JavaDoc>() {
175                     public T call() throws IOException JavaDoc {
176                         return callable.invoke(new File JavaDoc(remote), Channel.current());
177                     }
178
179                     public ClassLoader JavaDoc getClassLoader() {
180                         return callable.getClass().getClassLoader();
181                     }
182
183                     private static final long serialVersionUID = 1L;
184                 });
185             } catch (IOException JavaDoc e) {
186                 // wrap it into a new IOException so that we get the caller's stack trace as well.
187
throw new IOException2("remote file operation failed",e);
188             }
189         } else {
190             // the file is on the local machine.
191
return callable.invoke(new File JavaDoc(remote), Hudson.MasterComputer.localChannel);
192         }
193     }
194
195     /**
196      * Executes some program on the machine that this {@link FilePath} exists,
197      * so that one can perform local file operations.
198      */

199     public <V,E extends Throwable JavaDoc> V act(Callable<V,E> callable) throws IOException JavaDoc, InterruptedException JavaDoc, E {
200         if(channel!=null) {
201             // run this on a remote system
202
return channel.call(callable);
203         } else {
204             // the file is on the local machine
205
return callable.call();
206         }
207     }
208
209     /**
210      * Converts this file to the URI, relative to the machine
211      * on which this file is available.
212      */

213     public URI JavaDoc toURI() throws IOException JavaDoc, InterruptedException JavaDoc {
214         return act(new FileCallable<URI JavaDoc>() {
215             public URI JavaDoc invoke(File JavaDoc f, VirtualChannel channel) {
216                 return f.toURI();
217             }
218         });
219     }
220
221     /**
222      * Creates this directory.
223      */

224     public void mkdirs() throws IOException JavaDoc, InterruptedException JavaDoc {
225         if(act(new FileCallable<Boolean JavaDoc>() {
226             public Boolean JavaDoc invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc {
227                 return !f.mkdirs() && !f.exists();
228             }
229         }))
230             throw new IOException JavaDoc("Failed to mkdirs: "+remote);
231     }
232
233     /**
234      * Deletes this directory, including all its contents recursively.
235      */

236     public void deleteRecursive() throws IOException JavaDoc, InterruptedException JavaDoc {
237         act(new FileCallable<Void JavaDoc>() {
238             public Void JavaDoc invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc {
239                 Util.deleteRecursive(f);
240                 return null;
241             }
242         });
243     }
244
245     /**
246      * Deletes all the contents of this directory, but not the directory itself
247      */

248     public void deleteContents() throws IOException JavaDoc, InterruptedException JavaDoc {
249         act(new FileCallable<Void JavaDoc>() {
250             public Void JavaDoc invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc {
251                 Util.deleteContentsRecursive(f);
252                 return null;
253             }
254         });
255     }
256
257     /**
258      * Gets just the file name portion.
259      *
260      * This method assumes that the file name is the same between local and remote.
261      */

262     public String JavaDoc getName() {
263         int len = remote.length()-1;
264         while(len>=0) {
265             char ch = remote.charAt(len);
266             if(ch=='\\' || ch=='/')
267                 break;
268             len--;
269         }
270
271         return remote.substring(len+1);
272     }
273
274     /**
275      * The same as {@code new FilePath(this,rel)} but more OO.
276      */

277     public FilePath child(String JavaDoc rel) {
278         return new FilePath(this,rel);
279     }
280
281     /**
282      * Gets the parent file.
283      */

284     public FilePath getParent() {
285         int len = remote.length()-1;
286         while(len>=0) {
287             char ch = remote.charAt(len);
288             if(ch=='\\' || ch=='/')
289                 break;
290             len--;
291         }
292
293         return new FilePath( channel, remote.substring(0,len) );
294     }
295
296     /**
297      * Creates a temporary file.
298      */

299     public FilePath createTempFile(final String JavaDoc prefix, final String JavaDoc suffix) throws IOException JavaDoc, InterruptedException JavaDoc {
300         try {
301             return new FilePath(this,act(new FileCallable<String JavaDoc>() {
302                 public String JavaDoc invoke(File JavaDoc dir, VirtualChannel channel) throws IOException JavaDoc {
303                     File JavaDoc f = File.createTempFile(prefix, suffix, dir);
304                     return f.getName();
305                 }
306             }));
307         } catch (IOException JavaDoc e) {
308             throw new IOException2("Failed to create a temp file on "+remote,e);
309         }
310     }
311
312     /**
313      * Creates a temporary file in this directory and set the contents by the
314      * given text (encoded in the platform default encoding)
315      */

316     public FilePath createTextTempFile(final String JavaDoc prefix, final String JavaDoc suffix, final String JavaDoc contents) throws IOException JavaDoc, InterruptedException JavaDoc {
317         return createTextTempFile(prefix,suffix,contents,true);
318     }
319
320     /**
321      * Creates a temporary file in this directory and set the contents by the
322      * given text (encoded in the platform default encoding)
323      */

324     public FilePath createTextTempFile(final String JavaDoc prefix, final String JavaDoc suffix, final String JavaDoc contents, final boolean inThisDirectory) throws IOException JavaDoc, InterruptedException JavaDoc {
325         try {
326             return new FilePath(channel,act(new FileCallable<String JavaDoc>() {
327                 public String JavaDoc invoke(File JavaDoc dir, VirtualChannel channel) throws IOException JavaDoc {
328                     if(!inThisDirectory)
329                         dir = null;
330                     File JavaDoc f = File.createTempFile(prefix, suffix, dir);
331
332                     Writer JavaDoc w = new FileWriter JavaDoc(f);
333                     w.write(contents);
334                     w.close();
335
336                     return f.getAbsolutePath();
337                 }
338             }));
339         } catch (IOException JavaDoc e) {
340             throw new IOException2("Failed to create a temp file on "+remote,e);
341         }
342     }
343
344     /**
345      * Deletes this file.
346      */

347     public boolean delete() throws IOException JavaDoc, InterruptedException JavaDoc {
348         return act(new FileCallable<Boolean JavaDoc>() {
349             public Boolean JavaDoc invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc {
350                 return f.delete();
351             }
352         });
353     }
354
355     /**
356      * Checks if the file exists.
357      */

358     public boolean exists() throws IOException JavaDoc, InterruptedException JavaDoc {
359         return act(new FileCallable<Boolean JavaDoc>() {
360             public Boolean JavaDoc invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc {
361                 return f.exists();
362             }
363         });
364     }
365
366     /**
367      * Gets the last modified time stamp of this file, by using the clock
368      * of the machine where this file actually resides.
369      *
370      * @see File#lastModified()
371      */

372     public long lastModified() throws IOException JavaDoc, InterruptedException JavaDoc {
373         return act(new FileCallable<Long JavaDoc>() {
374             public Long JavaDoc invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc {
375                 return f.lastModified();
376             }
377         });
378     }
379
380     /**
381      * Checks if the file is a directory.
382      */

383     public boolean isDirectory() throws IOException JavaDoc, InterruptedException JavaDoc {
384         return act(new FileCallable<Boolean JavaDoc>() {
385             public Boolean JavaDoc invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc {
386                 return f.isDirectory();
387             }
388         });
389     }
390
391     /**
392      * List up files in this directory.
393      *
394      * @param filter
395      * The optional filter used to narrow down the result.
396      * If non-null, must be {@link Serializable}.
397      * If this {@link FilePath} represents a remote path,
398      * the filter object will be executed on the remote machine.
399      */

400     public List JavaDoc<FilePath> list(final FileFilter JavaDoc filter) throws IOException JavaDoc, InterruptedException JavaDoc {
401         return act(new FileCallable<List JavaDoc<FilePath>>() {
402             public List JavaDoc<FilePath> invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc {
403                 File JavaDoc[] children = f.listFiles(filter);
404                 if(children ==null) return null;
405
406                 ArrayList JavaDoc<FilePath> r = new ArrayList JavaDoc<FilePath>(children.length);
407                 for (File JavaDoc child : children)
408                     r.add(new FilePath(child));
409
410                 return r;
411             }
412         });
413     }
414
415     /**
416      * List up files in this directory that matches the given Ant-style filter.
417      *
418      * @param includes
419      * See {@link FileSet} for the syntax. String like "foo/*.zip".
420      */

421     public FilePath[] list(final String JavaDoc includes) throws IOException JavaDoc, InterruptedException JavaDoc {
422         return act(new FileCallable<FilePath[]>() {
423             public FilePath[] invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc {
424                 FileSet fs = new FileSet();
425                 fs.setDir(f);
426                 fs.setIncludes(includes);
427
428                 DirectoryScanner ds = fs.getDirectoryScanner(new org.apache.tools.ant.Project());
429                 String JavaDoc[] files = ds.getIncludedFiles();
430
431                 FilePath[] r = new FilePath[files.length];
432                 for( int i=0; i<r.length; i++ )
433                     r[i] = new FilePath(new File JavaDoc(f,files[i]));
434
435                 return r;
436             }
437         });
438     }
439
440     /**
441      * Reads this file.
442      */

443     public InputStream JavaDoc read() throws IOException JavaDoc {
444         if(channel==null)
445             return new FileInputStream JavaDoc(new File JavaDoc(remote));
446
447         final Pipe p = Pipe.createRemoteToLocal();
448         channel.callAsync(new Callable<Void JavaDoc,IOException JavaDoc>() {
449             public Void JavaDoc call() throws IOException JavaDoc {
450                 FileInputStream JavaDoc fis = new FileInputStream JavaDoc(new File JavaDoc(remote));
451                 Util.copyStream(fis,p.getOut());
452                 fis.close();
453                 p.getOut().close();
454                 return null;
455             }
456         });
457
458         return p.getIn();
459     }
460
461     /**
462      * Writes to this file.
463      * If this file already exists, it will be overwritten.
464      * If the directory doesn't exist, it will be created.
465      */

466     public OutputStream write() throws IOException JavaDoc, InterruptedException JavaDoc {
467         if(channel==null) {
468             File JavaDoc f = new File JavaDoc(remote);
469             f.getParentFile().mkdirs();
470             return new FileOutputStream JavaDoc(f);
471         }
472
473         return channel.call(new Callable<OutputStream,IOException JavaDoc>() {
474             public OutputStream call() throws IOException JavaDoc {
475                 File JavaDoc f = new File JavaDoc(remote);
476                 f.getParentFile().mkdirs();
477                 FileOutputStream JavaDoc fos = new FileOutputStream JavaDoc(f);
478                 return new RemoteOutputStream(fos);
479             }
480         });
481     }
482
483     /**
484      * Computes the MD5 digest of the file in hex string.
485      */

486     public String JavaDoc digest() throws IOException JavaDoc, InterruptedException JavaDoc {
487         return act(new FileCallable<String JavaDoc>() {
488             public String JavaDoc invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc {
489                 return Util.getDigestOf(new FileInputStream JavaDoc(f));
490             }
491         });
492     }
493
494     /**
495      * Copies this file to the specified target.
496      */

497     public void copyTo(FilePath target) throws IOException JavaDoc, InterruptedException JavaDoc {
498         OutputStream out = target.write();
499         try {
500             copyTo(out);
501         } finally {
502             out.close();
503         }
504     }
505
506     /**
507      * Sends the contents of this file into the given {@link OutputStream}.
508      */

509     public void copyTo(OutputStream os) throws IOException JavaDoc, InterruptedException JavaDoc {
510         final OutputStream out = new RemoteOutputStream(os);
511
512         act(new FileCallable<Void JavaDoc>() {
513             public Void JavaDoc invoke(File JavaDoc f, VirtualChannel channel) throws IOException JavaDoc {
514                 FileInputStream JavaDoc fis = new FileInputStream JavaDoc(f);
515                 Util.copyStream(fis,out);
516                 fis.close();
517                 out.close();
518                 return null;
519             }
520         });
521     }
522
523     /**
524      * Remoting interface used for {@link FilePath#copyRecursiveTo(String, FilePath)}.
525      *
526      * TODO: this might not be the most efficient way to do the copy.
527      */

528     interface RemoteCopier {
529         /**
530          * @param fileName
531          * relative path name to the output file. Path separator must be '/'.
532          */

533         void open(String JavaDoc fileName) throws IOException JavaDoc;
534         void write(byte[] buf, int len) throws IOException JavaDoc;
535         void close() throws IOException JavaDoc;
536     }
537
538     public int copyRecursiveTo(String JavaDoc fileMask, FilePath target) throws IOException JavaDoc, InterruptedException JavaDoc {
539         return copyRecursiveTo(fileMask,null,target);
540     }
541
542     /**
543      * Copies the files that match the given file mask to the specified target node.
544      *
545      * @param excludes
546      * Files to be excluded. Can be null.
547      * @return
548      * the number of files copied.
549      */

550     public int copyRecursiveTo(final String JavaDoc fileMask, final String JavaDoc excludes, final FilePath target) throws IOException JavaDoc, InterruptedException JavaDoc {
551         if(this.channel==target.channel) {
552             // local to local copy.
553
return act(new FileCallable<Integer JavaDoc>() {
554                 public Integer JavaDoc invoke(File JavaDoc base, VirtualChannel channel) throws IOException JavaDoc {
555                     assert target.channel==null;
556
557                     try {
558                         class CopyImpl extends Copy {
559                             private int copySize;
560
561                             public CopyImpl() {
562                                 setProject(new org.apache.tools.ant.Project());
563                             }
564
565                             protected void doFileOperations() {
566                                 copySize = super.fileCopyMap.size();
567                                 super.doFileOperations();
568                             }
569
570                             public int getNumCopied() {
571                                 return copySize;
572                             }
573                         }
574
575                         CopyImpl copyTask = new CopyImpl();
576                         copyTask.setTodir(new File JavaDoc(target.remote));
577                         FileSet src = new FileSet();
578                         src.setDir(base);
579                         src.setIncludes(fileMask);
580                         src.setExcludes(excludes);
581                         copyTask.addFileset(src);
582
583                         copyTask.execute();
584                         return copyTask.getNumCopied();
585                     } catch (BuildException e) {
586                         throw new IOException2("Failed to copy "+base+"/"+fileMask+" to "+target,e);
587                     }
588                 }
589             });
590         } else {
591             // remote copy
592
final FilePath src = this;
593
594             return target.act(new FileCallable<Integer JavaDoc>() {
595                 // this code is executed on the node that receives files.
596
public Integer JavaDoc invoke(final File JavaDoc dest, VirtualChannel channel) throws IOException JavaDoc {
597                     final RemoteCopier copier = src.getChannel().export(
598                         RemoteCopier.class,
599                         new RemoteCopier() {
600                             private OutputStream os;
601                             public void open(String JavaDoc fileName) throws IOException JavaDoc {
602                                 File JavaDoc file = new File JavaDoc(dest, fileName);
603                                 file.getParentFile().mkdirs();
604                                 os = new FileOutputStream JavaDoc(file);
605                             }
606
607                             public void write(byte[] buf, int len) throws IOException JavaDoc {
608                                 os.write(buf,0,len);
609                             }
610
611                             public void close() throws IOException JavaDoc {
612                                 os.close();
613                                 os = null;
614                             }
615                         });
616
617                     try {
618                         return src.act(new FileCallable<Integer JavaDoc>() {
619                             public Integer JavaDoc invoke(File JavaDoc base, VirtualChannel channel) throws IOException JavaDoc {
620                                 // copy to a remote node
621
FileSet fs = new FileSet();
622                                 fs.setDir(base);
623                                 fs.setIncludes(fileMask);
624
625                                 byte[] buf = new byte[8192];
626
627                                 DirectoryScanner ds = fs.getDirectoryScanner(new org.apache.tools.ant.Project());
628                                 String JavaDoc[] files = ds.getIncludedFiles();
629                                 for( String JavaDoc f : files) {
630                                     File JavaDoc file = new File JavaDoc(base, f);
631
632                                     if(Functions.isWindows())
633                                         f = f.replace('\\','/');
634                                     copier.open(f);
635
636                                     FileInputStream JavaDoc in = new FileInputStream JavaDoc(file);
637                                     int len;
638                                     while((len=in.read(buf))>=0)
639                                         copier.write(buf,len);
640                                     in.close();
641
642                                     copier.close();
643                                 }
644                                 return files.length;
645                             }
646                         });
647                     } catch (InterruptedException JavaDoc e) {
648                         throw new IOException2("Copy operation interrupted",e);
649                     }
650                 }
651             });
652         }
653     }
654
655     @Deprecated JavaDoc
656     public String JavaDoc toString() {
657         // to make writing JSPs easily, return local
658
return remote;
659     }
660
661     public VirtualChannel getChannel() {
662         if(channel!=null) return channel;
663         else return Hudson.MasterComputer.localChannel;
664     }
665
666     private void writeObject(ObjectOutputStream JavaDoc oos) throws IOException JavaDoc {
667         Channel target = Channel.current();
668
669         if(channel!=null && channel!=target)
670             throw new IllegalStateException JavaDoc("Can't send a remote FilePath to a different remote channel");
671
672         oos.defaultWriteObject();
673         oos.writeBoolean(channel==null);
674     }
675
676     private void readObject(ObjectInputStream JavaDoc ois) throws IOException JavaDoc, ClassNotFoundException JavaDoc {
677         Channel channel = Channel.current();
678         assert channel!=null;
679
680         ois.defaultReadObject();
681         if(ois.readBoolean()) {
682             this.channel = channel;
683         } else {
684             this.channel = null;
685         }
686     }
687
688     private static final long serialVersionUID = 1L;
689 }
690
Popular Tags