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