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 the first directory (of {@link #getScanDirs()} being scanned)
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 the filename filter
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      */
314     public synchronized void scanFiles ()
315     {
316         if (_scanDirs==null)
317             return;
318         
319         _currentScan.clear();
320         Iterator<File> itor = _scanDirs.iterator();
321         while (itor.hasNext())
322         {
323             File dir = itor.next();
324             
325             if ((dir != null) && (dir.exists()))
326                 scanFile(dir, _currentScan,0);
327         }
328     }
329 
330 
331     /**
332      * Report the adds/changes/removes to the registered listeners
333      * 
334      * @param currentScan the info from the most recent pass
335      * @param oldScan info from the previous pass
336      */
337     public void reportDifferences (Map currentScan, Map oldScan) 
338     {
339         List bulkChanges = new ArrayList();
340         
341         Set oldScanKeys = new HashSet(oldScan.keySet());
342         Iterator itor = currentScan.entrySet().iterator();
343         while (itor.hasNext())
344         {
345             Map.Entry entry = (Map.Entry)itor.next();
346             if (!oldScanKeys.contains(entry.getKey()))
347             {
348                 Log.debug("File added: "+entry.getKey());
349                 reportAddition ((String)entry.getKey());
350                 bulkChanges.add(entry.getKey());
351             }
352             else if (!oldScan.get(entry.getKey()).equals(entry.getValue()))
353             {
354                 Log.debug("File changed: "+entry.getKey());
355                 reportChange((String)entry.getKey());
356                 oldScanKeys.remove(entry.getKey());
357                 bulkChanges.add(entry.getKey());
358             }
359             else
360                 oldScanKeys.remove(entry.getKey());
361         }
362 
363         if (!oldScanKeys.isEmpty())
364         {
365 
366             Iterator keyItor = oldScanKeys.iterator();
367             while (keyItor.hasNext())
368             {
369                 String filename = (String)keyItor.next();
370                 Log.debug("File removed: "+filename);
371                 reportRemoval(filename);
372                 bulkChanges.add(filename);
373             }
374         }
375         
376         if (!bulkChanges.isEmpty())
377             reportBulkChanges(bulkChanges);
378     }
379 
380 
381     /**
382      * Get last modified time on a single file or recurse if
383      * the file is a directory. 
384      * @param f file or directory
385      * @param scanInfoMap map of filenames to last modified times
386      */
387     private void scanFile (File f, Map scanInfoMap, int depth)
388     {
389         try
390         {
391             if (!f.exists())
392                 return;
393 
394             if (f.isFile() || depth>0&& _reportDirs && f.isDirectory())
395             {
396                 if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
397                 {
398                     String name = f.getCanonicalPath();
399                     long lastModified = f.lastModified();
400                     scanInfoMap.put(name, new Long(lastModified));
401                 }
402             }
403             
404             if (f.isDirectory() && (_recursive || _scanDirs.contains(f)))
405             {
406                 File[] files = f.listFiles();
407                 for (int i=0;i<files.length;i++)
408                     scanFile(files[i], scanInfoMap,depth+1);
409             }
410         }
411         catch (IOException e)
412         {
413             Log.warn("Error scanning watched files", e);
414         }
415     }
416 
417     private void warn(Object listener,String filename,Throwable th)
418     {
419         Log.warn(th);
420         Log.warn(listener+" failed on '"+filename);
421     }
422 
423     /**
424      * Report a file addition to the registered FileAddedListeners
425      * @param filename
426      */
427     private void reportAddition (String filename)
428     {
429         Iterator itor = _listeners.iterator();
430         while (itor.hasNext())
431         {
432             Object l = itor.next();
433             try
434             {
435                 if (l instanceof DiscreteListener)
436                     ((DiscreteListener)l).fileAdded(filename);
437             }
438             catch (Exception e)
439             {
440                 warn(l,filename,e);
441             }
442             catch (Error e)
443             {
444                 warn(l,filename,e);
445             }
446         }
447     }
448 
449 
450     /**
451      * Report a file removal to the FileRemovedListeners
452      * @param filename
453      */
454     private void reportRemoval (String filename)
455     {
456         Iterator itor = _listeners.iterator();
457         while (itor.hasNext())
458         {
459             Object l = itor.next();
460             try
461             {
462                 if (l instanceof DiscreteListener)
463                     ((DiscreteListener)l).fileRemoved(filename);
464             }
465             catch (Exception e)
466             {
467                 warn(l,filename,e);
468             }
469             catch (Error e)
470             {
471                 warn(l,filename,e);
472             }
473         }
474     }
475 
476 
477     /**
478      * Report a file change to the FileChangedListeners
479      * @param filename
480      */
481     private void reportChange (String filename)
482     {
483         Iterator itor = _listeners.iterator();
484         while (itor.hasNext())
485         {
486             Object l = itor.next();
487             try
488             {
489                 if (l instanceof DiscreteListener)
490                     ((DiscreteListener)l).fileChanged(filename);
491             }
492             catch (Exception e)
493             {
494                 warn(l,filename,e);
495             }
496             catch (Error e)
497             {
498                 warn(l,filename,e);
499             }
500         }
501     }
502     
503     private void reportBulkChanges (List filenames)
504     {
505         Iterator itor = _listeners.iterator();
506         while (itor.hasNext())
507         {
508             Object l = itor.next();
509             try
510             {
511                 if (l instanceof BulkListener)
512                     ((BulkListener)l).filesChanged(filenames);
513             }
514             catch (Exception e)
515             {
516                 warn(l,filenames.toString(),e);
517             }
518             catch (Error e)
519             {
520                 warn(l,filenames.toString(),e);
521             }
522         }
523     }
524 
525 }