View Javadoc

1   // ========================================================================
2   // Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  
15  package org.eclipse.jetty.util;
16  
17  import java.io.File;
18  import java.io.FilenameFilter;
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Map.Entry;
28  import java.util.Queue;
29  import java.util.Set;
30  import java.util.Timer;
31  import java.util.TimerTask;
32  import java.util.concurrent.ConcurrentLinkedQueue;
33  
34  import org.eclipse.jetty.util.log.Log;
35  
36  
37  /**
38   * Scanner
39   * 
40   * Utility for scanning a directory for added, removed and changed
41   * files and reporting these events via registered Listeners.
42   *
43   * TODO AbstractLifeCycle
44   */
45  public class Scanner
46  {
47      private static int __scannerId=0;
48      private int _scanInterval;
49      private final List<Listener> _listeners = new ArrayList<Listener>();
50      private final Map<String,Long> _prevScan = new HashMap<String,Long> ();
51      private final Map<String,Long> _currentScan = new HashMap<String,Long> ();
52      private FilenameFilter _filter;
53      private final List<File> _scanDirs = new ArrayList<File>();
54      private volatile boolean _running = false;
55      private boolean _reportExisting = true;
56      private boolean _reportDirs = true;
57      private Timer _timer;
58      private TimerTask _task;
59      private int _scanDepth=0;
60  
61  
62      /**
63       * Listener
64       * 
65       * Marker for notifications re file changes.
66       */
67      public interface Listener
68      {
69      }
70  
71      
72      public interface DiscreteListener extends Listener
73      {
74          public void fileChanged (String filename) throws Exception;
75          public void fileAdded (String filename) throws Exception;
76          public void fileRemoved (String filename) throws Exception;
77      }
78      
79      
80      public interface BulkListener extends Listener
81      {
82          public void filesChanged (List<String> filenames) throws Exception;
83      }
84  
85  
86      /**
87       * 
88       */
89      public Scanner ()
90      {       
91      }
92  
93      /**
94       * Get the scan interval
95       * @return interval between scans in seconds
96       */
97      public int getScanInterval()
98      {
99          return _scanInterval;
100     }
101 
102     /**
103      * Set the scan interval
104      * @param scanInterval pause between scans in seconds, or 0 for no scan after the initial scan.
105      */
106     public synchronized void setScanInterval(int scanInterval)
107     {
108         _scanInterval = scanInterval;
109         schedule();
110     }
111 
112     /**
113      * Set the location of the directory to scan.
114      * @param dir
115      * @deprecated use setScanDirs(List dirs) instead
116      */
117     @Deprecated
118     public void setScanDir (File dir)
119     {
120         _scanDirs.clear(); 
121         _scanDirs.add(dir);
122     }
123 
124     /**
125      * Get the location of the directory to scan
126      * @return the first directory (of {@link #getScanDirs()} being scanned)
127      * @deprecated use getScanDirs() instead
128      */
129     @Deprecated
130     public File getScanDir ()
131     {
132         return (_scanDirs==null?null:(File)_scanDirs.get(0));
133     }
134 
135     public void setScanDirs (List<File> dirs)
136     {
137         _scanDirs.clear(); 
138         _scanDirs.addAll(dirs);
139     }
140     
141     public List<File> getScanDirs ()
142     {
143         return Collections.unmodifiableList(_scanDirs);
144     }
145     
146     /* ------------------------------------------------------------ */
147     /**
148      * @param recursive True if scanning is recursive
149      * @see  #setScanDepth()
150      */
151     public void setRecursive (boolean recursive)
152     {
153         _scanDepth=recursive?-1:0;
154     }
155     
156     /* ------------------------------------------------------------ */
157     /**
158      * @return True if scanning is fully recursive (scandepth==-1)
159      * @see #getScanDepth()
160      */
161     public boolean getRecursive ()
162     {
163         return _scanDepth==-1;
164     }
165     
166     /* ------------------------------------------------------------ */
167     /** Get the scanDepth.
168      * @return the scanDepth
169      */
170     public int getScanDepth()
171     {
172         return _scanDepth;
173     }
174 
175     /* ------------------------------------------------------------ */
176     /** Set the scanDepth.
177      * @param scanDepth the scanDepth to set
178      */
179     public void setScanDepth(int scanDepth)
180     {
181         _scanDepth = scanDepth;
182     }
183 
184     /**
185      * Apply a filter to files found in the scan directory.
186      * Only files matching the filter will be reported as added/changed/removed.
187      * @param filter
188      */
189     public void setFilenameFilter (FilenameFilter filter)
190     {
191         _filter = filter;
192     }
193 
194     /**
195      * Get any filter applied to files in the scan dir.
196      * @return the filename filter
197      */
198     public FilenameFilter getFilenameFilter ()
199     {
200         return _filter;
201     }
202 
203     /* ------------------------------------------------------------ */
204     /**
205      * Whether or not an initial scan will report all files as being
206      * added.
207      * @param reportExisting if true, all files found on initial scan will be 
208      * reported as being added, otherwise not
209      */
210     public void setReportExistingFilesOnStartup (boolean reportExisting)
211     {
212         _reportExisting = reportExisting;
213     }
214 
215     /* ------------------------------------------------------------ */
216     public boolean getReportExistingFilesOnStartup()
217     {
218         return _reportExisting;
219     }
220     
221     /* ------------------------------------------------------------ */
222     /** Set if found directories should be reported.
223      * @param dirs
224      */
225     public void setReportDirs(boolean dirs)
226     {
227         _reportDirs=dirs;
228     }
229     
230     /* ------------------------------------------------------------ */
231     public boolean getReportDirs()
232     {
233         return _reportDirs;
234     }
235     
236     /* ------------------------------------------------------------ */
237     /**
238      * Add an added/removed/changed listener
239      * @param listener
240      */
241     public synchronized void addListener (Listener listener)
242     {
243         if (listener == null)
244             return;
245         _listeners.add(listener);   
246     }
247 
248 
249 
250     /**
251      * Remove a registered listener
252      * @param listener the Listener to be removed
253      */
254     public synchronized void removeListener (Listener listener)
255     {
256         if (listener == null)
257             return;
258         _listeners.remove(listener);    
259     }
260 
261 
262     /**
263      * Start the scanning action.
264      */
265     public synchronized void start ()
266     {
267         if (_running)
268             return;
269 
270         _running = true;
271 
272         if (_reportExisting)
273         {
274             // if files exist at startup, report them
275             scan();
276         }
277         else
278         {
279             //just register the list of existing files and only report changes
280             scanFiles();
281             _prevScan.putAll(_currentScan);
282         }
283         schedule();
284     }
285 
286     public TimerTask newTimerTask ()
287     {
288         return new TimerTask()
289         {
290             @Override
291             public void run() { scan(); }
292         };
293     }
294 
295     public Timer newTimer ()
296     {
297         return new Timer("Scanner-"+__scannerId++, true);
298     }
299     
300     public void schedule ()
301     {  
302         if (_running)
303         {
304             if (_timer!=null)
305                 _timer.cancel();
306             if (_task!=null)
307                 _task.cancel();
308             if (getScanInterval() > 0)
309             {
310                 _timer = newTimer();
311                 _task = newTimerTask();
312                 _timer.schedule(_task, 1000L*getScanInterval(),1000L*getScanInterval());
313             }
314         }
315     }
316     /**
317      * Stop the scanning.
318      */
319     public synchronized void stop ()
320     {
321         if (_running)
322         {
323             _running = false; 
324             if (_timer!=null)
325                 _timer.cancel();
326             if (_task!=null)
327                 _task.cancel();
328             _task=null;
329             _timer=null;
330         }
331     }
332 
333     /**
334      * Perform a pass of the scanner and report changes
335      */
336     public synchronized void scan ()
337     {
338         scanFiles();
339         reportDifferences(_currentScan, _prevScan);
340         _prevScan.clear();
341         _prevScan.putAll(_currentScan);
342     }
343 
344     /**
345      * Recursively scan all files in the designated directories.
346      */
347     public synchronized void scanFiles ()
348     {
349         if (_scanDirs==null)
350             return;
351         
352         _currentScan.clear();
353         Iterator<File> itor = _scanDirs.iterator();
354         while (itor.hasNext())
355         {
356             File dir = itor.next();
357             
358             if ((dir != null) && (dir.exists()))
359                 scanFile(dir, _currentScan,0);
360         }
361     }
362 
363 
364     /**
365      * Report the adds/changes/removes to the registered listeners
366      * 
367      * @param currentScan the info from the most recent pass
368      * @param oldScan info from the previous pass
369      */
370     public void reportDifferences (Map<String,Long> currentScan, Map<String,Long> oldScan) 
371     {
372         List<String> bulkChanges = new ArrayList<String>();
373         
374         Set<String> oldScanKeys = new HashSet<String>(oldScan.keySet());
375         Iterator<Entry<String, Long>> itor = currentScan.entrySet().iterator();
376         while (itor.hasNext())
377         {
378             Map.Entry<String, Long> entry = itor.next();
379             if (!oldScanKeys.contains(entry.getKey()))
380             {
381                 Log.debug("File added: "+entry.getKey());
382                 reportAddition ((String)entry.getKey());
383                 bulkChanges.add(entry.getKey());
384             }
385             else if (!oldScan.get(entry.getKey()).equals(entry.getValue()))
386             {
387                 Log.debug("File changed: "+entry.getKey());
388                 reportChange((String)entry.getKey());
389                 oldScanKeys.remove(entry.getKey());
390                 bulkChanges.add(entry.getKey());
391             }
392             else
393                 oldScanKeys.remove(entry.getKey());
394         }
395 
396         if (!oldScanKeys.isEmpty())
397         {
398 
399             Iterator<String> keyItor = oldScanKeys.iterator();
400             while (keyItor.hasNext())
401             {
402                 String filename = (String)keyItor.next();
403                 Log.debug("File removed: "+filename);
404                 reportRemoval(filename);
405                 bulkChanges.add(filename);
406             }
407         }
408         
409         if (!bulkChanges.isEmpty())
410             reportBulkChanges(bulkChanges);
411     }
412 
413 
414     /**
415      * Get last modified time on a single file or recurse if
416      * the file is a directory. 
417      * @param f file or directory
418      * @param scanInfoMap map of filenames to last modified times
419      */
420     private void scanFile (File f, Map<String,Long> scanInfoMap, int depth)
421     {
422         try
423         {
424             if (!f.exists())
425                 return;
426 
427             if (f.isFile() || depth>0&& _reportDirs && f.isDirectory())
428             {
429                 if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
430                 {
431                     String name = f.getCanonicalPath();
432                     long lastModified = f.lastModified();
433                     scanInfoMap.put(name, new Long(lastModified));
434                 }
435             }
436             
437             // If it is a directory, scan if it is a known directory or the depth is OK.
438             if (f.isDirectory() && (depth<_scanDepth || _scanDepth==-1 || _scanDirs.contains(f)))
439             {
440                 File[] files = f.listFiles();
441                 for (int i=0;i<files.length;i++)
442                     scanFile(files[i], scanInfoMap,depth+1);
443             }
444         }
445         catch (IOException e)
446         {
447             Log.warn("Error scanning watched files", e);
448         }
449     }
450 
451     private void warn(Object listener,String filename,Throwable th)
452     {
453         Log.warn(th);
454         Log.warn(listener+" failed on '"+filename);
455     }
456 
457     /**
458      * Report a file addition to the registered FileAddedListeners
459      * @param filename
460      */
461     private void reportAddition (String filename)
462     {
463         Iterator<Listener> itor = _listeners.iterator();
464         while (itor.hasNext())
465         {
466             Listener l = itor.next();
467             try
468             {
469                 if (l instanceof DiscreteListener)
470                     ((DiscreteListener)l).fileAdded(filename);
471             }
472             catch (Exception e)
473             {
474                 warn(l,filename,e);
475             }
476             catch (Error e)
477             {
478                 warn(l,filename,e);
479             }
480         }
481     }
482 
483 
484     /**
485      * Report a file removal to the FileRemovedListeners
486      * @param filename
487      */
488     private void reportRemoval (String filename)
489     {
490         Iterator<Listener> itor = _listeners.iterator();
491         while (itor.hasNext())
492         {
493             Object l = itor.next();
494             try
495             {
496                 if (l instanceof DiscreteListener)
497                     ((DiscreteListener)l).fileRemoved(filename);
498             }
499             catch (Exception e)
500             {
501                 warn(l,filename,e);
502             }
503             catch (Error e)
504             {
505                 warn(l,filename,e);
506             }
507         }
508     }
509 
510 
511     /**
512      * Report a file change to the FileChangedListeners
513      * @param filename
514      */
515     private void reportChange (String filename)
516     {
517         Iterator<Listener> itor = _listeners.iterator();
518         while (itor.hasNext())
519         {
520             Listener l = itor.next();
521             try
522             {
523                 if (l instanceof DiscreteListener)
524                     ((DiscreteListener)l).fileChanged(filename);
525             }
526             catch (Exception e)
527             {
528                 warn(l,filename,e);
529             }
530             catch (Error e)
531             {
532                 warn(l,filename,e);
533             }
534         }
535     }
536     
537     private void reportBulkChanges (List<String> filenames)
538     {
539         Iterator<Listener> itor = _listeners.iterator();
540         while (itor.hasNext())
541         {
542             Listener l = itor.next();
543             try
544             {
545                 if (l instanceof BulkListener)
546                     ((BulkListener)l).filesChanged(filenames);
547             }
548             catch (Exception e)
549             {
550                 warn(l,filenames.toString(),e);
551             }
552             catch (Error e)
553             {
554                 warn(l,filenames.toString(),e);
555             }
556         }
557     }
558 
559 }