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