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