]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/util/Scanner.java
updating jetty to jetty-9.2.16.v2016040
[gigi.git] / lib / jetty / org / eclipse / jetty / util / Scanner.java
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4 //  ------------------------------------------------------------------------
5 //  All rights reserved. This program and the accompanying materials
6 //  are made available under the terms of the Eclipse Public License v1.0
7 //  and Apache License v2.0 which accompanies this distribution.
8 //
9 //      The Eclipse Public License is available at
10 //      http://www.eclipse.org/legal/epl-v10.html
11 //
12 //      The Apache License v2.0 is available at
13 //      http://www.opensource.org/licenses/apache2.0.php
14 //
15 //  You may elect to redistribute this code under either of these licenses.
16 //  ========================================================================
17 //
18
19
20 package org.eclipse.jetty.util;
21
22 import java.io.File;
23 import java.io.FilenameFilter;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.Set;
34 import java.util.Timer;
35 import java.util.TimerTask;
36
37 import org.eclipse.jetty.util.component.AbstractLifeCycle;
38 import org.eclipse.jetty.util.log.Log;
39 import org.eclipse.jetty.util.log.Logger;
40
41
42 /**
43  * Scanner
44  * 
45  * Utility for scanning a directory for added, removed and changed
46  * files and reporting these events via registered Listeners.
47  *
48  */
49 public class Scanner extends AbstractLifeCycle
50 {
51     private static final Logger LOG = Log.getLogger(Scanner.class);
52     private static int __scannerId=0;
53     private int _scanInterval;
54     private int _scanCount = 0;
55     private final List<Listener> _listeners = new ArrayList<Listener>();
56     private final Map<String,TimeNSize> _prevScan = new HashMap<String,TimeNSize> ();
57     private final Map<String,TimeNSize> _currentScan = new HashMap<String,TimeNSize> ();
58     private FilenameFilter _filter;
59     private final List<File> _scanDirs = new ArrayList<File>();
60     private volatile boolean _running = false;
61     private boolean _reportExisting = true;
62     private boolean _reportDirs = true;
63     private Timer _timer;
64     private TimerTask _task;
65     private int _scanDepth=0;
66     
67     public enum Notification { ADDED, CHANGED, REMOVED };
68     private final Map<String,Notification> _notifications = new HashMap<String,Notification>();
69
70     static class TimeNSize
71     {
72         final long _lastModified;
73         final long _size;
74         
75         public TimeNSize(long lastModified, long size)
76         {
77             _lastModified = lastModified;
78             _size = size;
79         }
80         
81         @Override
82         public int hashCode()
83         {
84             return (int)_lastModified^(int)_size;
85         }
86         
87         @Override
88         public boolean equals(Object o)
89         {
90             if (o instanceof TimeNSize)
91             {
92                 TimeNSize tns = (TimeNSize)o;
93                 return tns._lastModified==_lastModified && tns._size==_size;
94             }
95             return false;
96         }
97         
98         @Override
99         public String toString()
100         {
101             return "[lm="+_lastModified+",s="+_size+"]";
102         }
103     }
104     
105     /**
106      * Listener
107      * 
108      * Marker for notifications re file changes.
109      */
110     public interface Listener
111     {
112     }
113
114     public interface ScanListener extends Listener
115     {
116         public void scan();
117     }
118     
119     public interface DiscreteListener extends Listener
120     {
121         public void fileChanged (String filename) throws Exception;
122         public void fileAdded (String filename) throws Exception;
123         public void fileRemoved (String filename) throws Exception;
124     }
125     
126     
127     public interface BulkListener extends Listener
128     {
129         public void filesChanged (List<String> filenames) throws Exception;
130     }
131
132     /**
133      * Listener that notifies when a scan has started and when it has ended.
134      */
135     public interface ScanCycleListener extends Listener
136     {
137         public void scanStarted(int cycle) throws Exception;
138         public void scanEnded(int cycle) throws Exception;
139     }
140
141     /**
142      * 
143      */
144     public Scanner ()
145     {       
146     }
147
148     /**
149      * Get the scan interval
150      * @return interval between scans in seconds
151      */
152     public synchronized int getScanInterval()
153     {
154         return _scanInterval;
155     }
156
157     /**
158      * Set the scan interval
159      * @param scanInterval pause between scans in seconds, or 0 for no scan after the initial scan.
160      */
161     public synchronized void setScanInterval(int scanInterval)
162     {
163         _scanInterval = scanInterval;
164         schedule();
165     }
166
167     public void setScanDirs (List<File> dirs)
168     {
169         _scanDirs.clear(); 
170         _scanDirs.addAll(dirs);
171     }
172     
173     public synchronized void addScanDir( File dir )
174     {
175         _scanDirs.add( dir );
176     }
177     
178     public List<File> getScanDirs ()
179     {
180         return Collections.unmodifiableList(_scanDirs);
181     }
182     
183     /* ------------------------------------------------------------ */
184     /**
185      * @param recursive True if scanning is recursive
186      * @see  #setScanDepth(int)
187      */
188     public void setRecursive (boolean recursive)
189     {
190         _scanDepth=recursive?-1:0;
191     }
192     
193     /* ------------------------------------------------------------ */
194     /**
195      * @return True if scanning is fully recursive (scandepth==-1)
196      * @see #getScanDepth()
197      */
198     public boolean getRecursive ()
199     {
200         return _scanDepth==-1;
201     }
202     
203     /* ------------------------------------------------------------ */
204     /** Get the scanDepth.
205      * @return the scanDepth
206      */
207     public int getScanDepth()
208     {
209         return _scanDepth;
210     }
211
212     /* ------------------------------------------------------------ */
213     /** Set the scanDepth.
214      * @param scanDepth the scanDepth to set
215      */
216     public void setScanDepth(int scanDepth)
217     {
218         _scanDepth = scanDepth;
219     }
220
221     /**
222      * Apply a filter to files found in the scan directory.
223      * Only files matching the filter will be reported as added/changed/removed.
224      * @param filter
225      */
226     public void setFilenameFilter (FilenameFilter filter)
227     {
228         _filter = filter;
229     }
230
231     /**
232      * Get any filter applied to files in the scan dir.
233      * @return the filename filter
234      */
235     public FilenameFilter getFilenameFilter ()
236     {
237         return _filter;
238     }
239
240     /* ------------------------------------------------------------ */
241     /**
242      * Whether or not an initial scan will report all files as being
243      * added.
244      * @param reportExisting if true, all files found on initial scan will be 
245      * reported as being added, otherwise not
246      */
247     public void setReportExistingFilesOnStartup (boolean reportExisting)
248     {
249         _reportExisting = reportExisting;
250     }
251
252     /* ------------------------------------------------------------ */
253     public boolean getReportExistingFilesOnStartup()
254     {
255         return _reportExisting;
256     }
257     
258     /* ------------------------------------------------------------ */
259     /** Set if found directories should be reported.
260      * @param dirs
261      */
262     public void setReportDirs(boolean dirs)
263     {
264         _reportDirs=dirs;
265     }
266     
267     /* ------------------------------------------------------------ */
268     public boolean getReportDirs()
269     {
270         return _reportDirs;
271     }
272     
273     /* ------------------------------------------------------------ */
274     /**
275      * Add an added/removed/changed listener
276      * @param listener
277      */
278     public synchronized void addListener (Listener listener)
279     {
280         if (listener == null)
281             return;
282         _listeners.add(listener);   
283     }
284
285     /* ------------------------------------------------------------ */
286     /**
287      * Remove a registered listener
288      * @param listener the Listener to be removed
289      */
290     public synchronized void removeListener (Listener listener)
291     {
292         if (listener == null)
293             return;
294         _listeners.remove(listener);    
295     }
296
297
298     /**
299      * Start the scanning action.
300      */
301     @Override
302     public synchronized void doStart()
303     {
304         if (_running)
305             return;
306
307         _running = true;
308
309         if (_reportExisting)
310         {
311             // if files exist at startup, report them
312             scan();
313             scan(); // scan twice so files reported as stable
314         }
315         else
316         {
317             //just register the list of existing files and only report changes
318             scanFiles();
319             _prevScan.putAll(_currentScan);
320         }
321         schedule();
322     }
323
324     public TimerTask newTimerTask ()
325     {
326         return new TimerTask()
327         {
328             @Override
329             public void run() { scan(); }
330         };
331     }
332
333     public Timer newTimer ()
334     {
335         return new Timer("Scanner-"+__scannerId++, true);
336     }
337     
338     public void schedule ()
339     {  
340         if (_running)
341         {
342             if (_timer!=null)
343                 _timer.cancel();
344             if (_task!=null)
345                 _task.cancel();
346             if (getScanInterval() > 0)
347             {
348                 _timer = newTimer();
349                 _task = newTimerTask();
350                 _timer.schedule(_task, 1010L*getScanInterval(),1010L*getScanInterval());
351             }
352         }
353     }
354     /**
355      * Stop the scanning.
356      */
357     @Override
358     public synchronized void doStop()
359     {
360         if (_running)
361         {
362             _running = false; 
363             if (_timer!=null)
364                 _timer.cancel();
365             if (_task!=null)
366                 _task.cancel();
367             _task=null;
368             _timer=null;
369         }
370     }
371
372     /**
373      * @return true if the path exists in one of the scandirs
374      */
375     public boolean exists(String path)
376     {
377         for (File dir : _scanDirs)
378             if (new File(dir,path).exists())
379                 return true;
380         return false;
381     }
382     
383     
384     /**
385      * Perform a pass of the scanner and report changes
386      */
387     public synchronized void scan ()
388     {
389         reportScanStart(++_scanCount);
390         scanFiles();
391         reportDifferences(_currentScan, _prevScan);
392         _prevScan.clear();
393         _prevScan.putAll(_currentScan);
394         reportScanEnd(_scanCount);
395         
396         for (Listener l : _listeners)
397         {
398             try
399             {
400                 if (l instanceof ScanListener)
401                     ((ScanListener)l).scan();
402             }
403             catch (Exception e)
404             {
405                 LOG.warn(e);
406             }
407             catch (Error e)
408             {
409                 LOG.warn(e);
410             }
411         }
412     }
413
414     /**
415      * Recursively scan all files in the designated directories.
416      */
417     public synchronized void scanFiles ()
418     {
419         if (_scanDirs==null)
420             return;
421         
422         _currentScan.clear();
423         Iterator<File> itor = _scanDirs.iterator();
424         while (itor.hasNext())
425         {
426             File dir = itor.next();
427             
428             if ((dir != null) && (dir.exists()))
429                 try
430                 {
431                     scanFile(dir.getCanonicalFile(), _currentScan,0);
432                 }
433                 catch (IOException e)
434                 {
435                     LOG.warn("Error scanning files.", e);
436                 }
437         }
438     }
439
440
441     /**
442      * Report the adds/changes/removes to the registered listeners
443      * 
444      * @param currentScan the info from the most recent pass
445      * @param oldScan info from the previous pass
446      */
447     public synchronized void reportDifferences (Map<String,TimeNSize> currentScan, Map<String,TimeNSize> oldScan) 
448     {
449         // scan the differences and add what was found to the map of notifications:
450
451         Set<String> oldScanKeys = new HashSet<String>(oldScan.keySet());
452         
453         // Look for new and changed files
454         for (Map.Entry<String, TimeNSize> entry: currentScan.entrySet())
455         {
456             String file = entry.getKey(); 
457             if (!oldScanKeys.contains(file))
458             {
459                 Notification old=_notifications.put(file,Notification.ADDED);
460                 if (old!=null)
461                 { 
462                     switch(old)
463                     {
464                         case REMOVED: 
465                         case CHANGED:
466                             _notifications.put(file,Notification.CHANGED);
467                     }
468                 }
469             }
470             else if (!oldScan.get(file).equals(currentScan.get(file)))
471             {
472                 Notification old=_notifications.put(file,Notification.CHANGED);
473                 if (old!=null)
474                 {
475                     switch(old)
476                     {
477                         case ADDED:
478                             _notifications.put(file,Notification.ADDED);
479                     }
480                 }
481             }
482         }
483         
484         // Look for deleted files
485         for (String file : oldScan.keySet())
486         {
487             if (!currentScan.containsKey(file))
488             {
489                 Notification old=_notifications.put(file,Notification.REMOVED);
490                 if (old!=null)
491                 {
492                     switch(old)
493                     {
494                         case ADDED:
495                             _notifications.remove(file);
496                     }
497                 }
498             }
499         }
500         
501         if (LOG.isDebugEnabled())
502             LOG.debug("scanned "+_scanDirs+": "+_notifications);
503                 
504         // Process notifications
505         // Only process notifications that are for stable files (ie same in old and current scan).
506         List<String> bulkChanges = new ArrayList<String>();
507         for (Iterator<Entry<String,Notification>> iter = _notifications.entrySet().iterator();iter.hasNext();)
508         {
509             Entry<String,Notification> entry=iter.next();
510             String file=entry.getKey();
511             
512             // Is the file stable?
513             if (oldScan.containsKey(file))
514             {
515                 if (!oldScan.get(file).equals(currentScan.get(file)))
516                     continue;
517             }
518             else if (currentScan.containsKey(file))
519                 continue;
520                             
521             // File is stable so notify
522             Notification notification=entry.getValue();
523             iter.remove();
524             bulkChanges.add(file);
525             switch(notification)
526             {
527                 case ADDED:
528                     reportAddition(file);
529                     break;
530                 case CHANGED:
531                     reportChange(file);
532                     break;
533                 case REMOVED:
534                     reportRemoval(file);
535                     break;
536             }
537         }
538         if (!bulkChanges.isEmpty())
539             reportBulkChanges(bulkChanges);
540     }
541
542
543     /**
544      * Get last modified time on a single file or recurse if
545      * the file is a directory. 
546      * @param f file or directory
547      * @param scanInfoMap map of filenames to last modified times
548      */
549     private void scanFile (File f, Map<String,TimeNSize> scanInfoMap, int depth)
550     {
551         try
552         {
553             if (!f.exists())
554                 return;
555
556             if (f.isFile() || depth>0&& _reportDirs && f.isDirectory())
557             {
558                 if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
559                 {
560                     if (LOG.isDebugEnabled())
561                         LOG.debug("scan accepted {}",f);
562                     String name = f.getCanonicalPath();
563                     scanInfoMap.put(name, new TimeNSize(f.lastModified(),f.isDirectory()?0:f.length()));
564                 }
565                 else
566                 {
567                     if (LOG.isDebugEnabled())
568                         LOG.debug("scan rejected {}",f);
569                 }
570             }
571             
572             // If it is a directory, scan if it is a known directory or the depth is OK.
573             if (f.isDirectory() && (depth<_scanDepth || _scanDepth==-1 || _scanDirs.contains(f)))
574             {
575                 File[] files = f.listFiles();
576                 if (files != null)
577                 {
578                     for (int i=0;i<files.length;i++)
579                         scanFile(files[i], scanInfoMap,depth+1);
580                 }
581                 else
582                     LOG.warn("Error listing files in directory {}", f);
583             }
584         }
585         catch (IOException e)
586         {
587             LOG.warn("Error scanning watched files", e);
588         }
589     }
590
591     private void warn(Object listener,String filename,Throwable th)
592     {
593         LOG.warn(listener+" failed on '"+filename, th);
594     }
595
596     /**
597      * Report a file addition to the registered FileAddedListeners
598      * @param filename
599      */
600     private void reportAddition (String filename)
601     {
602         Iterator<Listener> itor = _listeners.iterator();
603         while (itor.hasNext())
604         {
605             Listener l = itor.next();
606             try
607             {
608                 if (l instanceof DiscreteListener)
609                     ((DiscreteListener)l).fileAdded(filename);
610             }
611             catch (Exception e)
612             {
613                 warn(l,filename,e);
614             }
615             catch (Error e)
616             {
617                 warn(l,filename,e);
618             }
619         }
620     }
621
622
623     /**
624      * Report a file removal to the FileRemovedListeners
625      * @param filename
626      */
627     private void reportRemoval (String filename)
628     {
629         Iterator<Listener> itor = _listeners.iterator();
630         while (itor.hasNext())
631         {
632             Object l = itor.next();
633             try
634             {
635                 if (l instanceof DiscreteListener)
636                     ((DiscreteListener)l).fileRemoved(filename);
637             }
638             catch (Exception e)
639             {
640                 warn(l,filename,e);
641             }
642             catch (Error e)
643             {
644                 warn(l,filename,e);
645             }
646         }
647     }
648
649
650     /**
651      * Report a file change to the FileChangedListeners
652      * @param filename
653      */
654     private void reportChange (String filename)
655     {
656         Iterator<Listener> itor = _listeners.iterator();
657         while (itor.hasNext())
658         {
659             Listener l = itor.next();
660             try
661             {
662                 if (l instanceof DiscreteListener)
663                     ((DiscreteListener)l).fileChanged(filename);
664             }
665             catch (Exception e)
666             {
667                 warn(l,filename,e);
668             }
669             catch (Error e)
670             {
671                 warn(l,filename,e);
672             }
673         }
674     }
675     
676     private void reportBulkChanges (List<String> filenames)
677     {
678         Iterator<Listener> itor = _listeners.iterator();
679         while (itor.hasNext())
680         {
681             Listener l = itor.next();
682             try
683             {
684                 if (l instanceof BulkListener)
685                     ((BulkListener)l).filesChanged(filenames);
686             }
687             catch (Exception e)
688             {
689                 warn(l,filenames.toString(),e);
690             }
691             catch (Error e)
692             {
693                 warn(l,filenames.toString(),e);
694             }
695         }
696     }
697     
698     /**
699      * signal any scan cycle listeners that a scan has started
700      */
701     private void reportScanStart(int cycle)
702     {
703         for (Listener listener : _listeners)
704         {
705             try
706             {
707                 if (listener instanceof ScanCycleListener)
708                 {
709                     ((ScanCycleListener)listener).scanStarted(cycle);
710                 }
711             }
712             catch (Exception e)
713             {
714                 LOG.warn(listener + " failed on scan start for cycle " + cycle, e);
715             }
716         }
717     }
718
719     /**
720      * sign
721      */
722     private void reportScanEnd(int cycle)
723     {
724         for (Listener listener : _listeners)
725         {
726             try
727             {
728                 if (listener instanceof ScanCycleListener)
729                 {
730                     ((ScanCycleListener)listener).scanEnded(cycle);
731                 }
732             }
733             catch (Exception e)
734             {
735                 LOG.warn(listener + " failed on scan end for cycle " + cycle, e);
736             }
737         }
738     }
739
740 }