View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.jetspeed.cache.file;
19  
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.Date;
23  import java.util.Map;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.LinkedList;
27  import java.util.Iterator;
28  import java.io.File;
29  import java.io.FileNotFoundException;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  /***
35   * FileCache keeps a cache of files up-to-date with a most simple eviction policy.
36   * The eviction policy will keep n items in the cache, and then start evicting
37   * the items ordered-by least used first. The cache runs a thread to check for
38   * both evictions and refreshes.
39   *
40   *  @author David S. Taylor <a href="mailto:taylor@apache.org">David Sean Taylor</a>
41   *  @version $Id: FileCache.java 516448 2007-03-09 16:25:47Z ate $
42   */
43  
44  public class FileCache implements java.util.Comparator
45  {
46      protected long scanRate = 300;  // every 5 minutes
47      protected int maxSize = 100; // maximum of 100 items
48      protected List listeners = new LinkedList();
49  
50      private FileCacheScanner scanner = null;
51      private Map cache = null;
52  
53      private final static Log log = LogFactory.getLog(FileCache.class);
54  
55      /***
56       * Default constructor. Use default values for scanReate and maxSize
57       *
58       */
59      public FileCache()
60      {
61          cache = Collections.synchronizedMap(new HashMap());
62          this.scanner = new FileCacheScanner();
63          this.scanner.setDaemon(true);
64      }
65  
66      /***
67       * Set scanRate and maxSize
68       *
69       * @param scanRate how often in seconds to refresh and evict from the cache
70       * @param maxSize the maximum allowed size of the cache before eviction starts
71       */
72      public FileCache(long scanRate, 
73                       int maxSize)
74      {
75          
76          cache = Collections.synchronizedMap(new HashMap());
77  
78          this.scanRate = scanRate;
79          this.maxSize = maxSize;
80          this.scanner = new FileCacheScanner();
81          this.scanner.setDaemon(true);
82      }
83  
84      /***
85       * Set all parameters on the cache
86       *
87       * @param initialCapacity the initial size of the cache as passed to HashMap
88       * @param loadFactor how full the hash table is allowed to get before increasing
89       * @param scanRate how often in seconds to refresh and evict from the cache
90       * @param maxSize the maximum allowed size of the cache before eviction starts
91       */
92      public FileCache(int initialCapacity, 
93                       int loadFactor, 
94                       long scanRate, 
95                       int maxSize)
96      {
97          cache = Collections.synchronizedMap(new HashMap(initialCapacity, loadFactor));
98  
99          this.scanRate = scanRate;
100         this.maxSize = maxSize;
101         this.scanner = new FileCacheScanner();
102         this.scanner.setDaemon(true);
103     }
104 
105     /***
106      * Set the new refresh scan rate on managed files.
107      *
108      * @param scanRate the new scan rate in seconds
109      */
110     public void setScanRate(long scanRate)
111     {
112         this.scanRate= scanRate;
113     }
114 
115     /***
116      * Get the refresh scan rate 
117      *
118      * @return the current refresh scan rate in seconds
119      */
120     public long getScanRate()
121     {
122         return scanRate;
123     }
124 
125     /***
126      * Set the new maximum size of the cache 
127      *
128      * @param maxSize the maximum size of the cache
129      */
130     public void setMaxSize(int maxSize)
131     {
132         this.maxSize = maxSize;
133     }
134 
135     /***
136      * Get the maximum size of the cache 
137      *
138      * @return the current maximum size of the cache
139      */
140     public int getMaxSize()
141     {
142         return maxSize;
143     }
144 
145     /***
146      * Gets an entry from the cache given a key
147      *
148      * @param key the key to look up the entry by
149      * @return the entry
150      */
151     public FileCacheEntry get(String key)
152     {
153         return (FileCacheEntry) cache.get(key);
154     }
155 
156     /***
157      * Gets an entry from the cache given a key
158      *
159      * @param key the key to look up the entry by
160      * @return the entry
161      */
162     public Object getDocument(String key)
163     {
164         FileCacheEntry entry = (FileCacheEntry) cache.get(key);
165         if (entry != null)
166         {
167             return entry.getDocument();
168         }
169         return null;
170     }
171 
172     /***
173      * Puts a file entry in the file cache
174      *
175      * @param file The file to be put in the cache
176      * @param document the cached document
177      */
178     public void put(File file, Object document)
179         throws java.io.IOException
180     {
181         if(!file.exists())
182         {
183             throw new FileNotFoundException("File to cache: "+file.getAbsolutePath()+" does not exist.");
184         }
185         FileCacheEntry entry = new FileCacheEntryImpl(file, document);
186         cache.put(file.getCanonicalPath(), entry);
187     }
188 
189     /***
190      * Puts a file entry in the file cache
191      *
192      * @param path the full path name of the file
193      * @param document the cached document
194      */
195     public void put(String key, Object document, File rootFile)
196         throws java.io.IOException
197     {
198         File file = new File(rootFile, key);
199         if(!file.exists())
200         {
201             throw new FileNotFoundException("File to cache: "+file.getAbsolutePath()+" does not exist.");
202         }
203         FileCacheEntry entry = new FileCacheEntryImpl(file, document);
204         cache.put(key, entry);
205     }
206 
207     /***
208      * Removes a file entry from the file cache
209      *
210      * @param key the full path name of the file
211      * @return the entry removed
212      */
213     public Object remove(String key)
214     {
215         return cache.remove(key);
216     }
217 
218 
219     /***
220      * Add a File Cache Event Listener 
221      *
222      * @param listener the event listener
223      */
224     public void addListener(FileCacheEventListener listener)
225     {
226         listeners.add(listener);
227     }
228 
229     /***
230      * Start the file Scanner running at the current scan rate.
231      *
232      */
233     public void startFileScanner()
234     {
235         try
236         {
237 
238             this.scanner.start();
239         }
240         catch (java.lang.IllegalThreadStateException e)
241         {
242             log.error("Exception starting scanner", e);
243         }
244     }
245 
246     /***
247      * Stop the file Scanner 
248      *
249      */
250     public void stopFileScanner()
251     {
252         this.scanner.setStopping(true);
253     }
254 
255     /***
256      * Evicts entries based on last accessed time stamp
257      *
258      */
259     protected void evict()        
260     {
261         synchronized (cache)
262         {
263             if (this.getMaxSize() >= cache.size())
264             {
265                 return;
266             }
267     
268             List list = new LinkedList(cache.values());
269             Collections.sort(list, this);
270     
271             int count = 0;
272             int limit = cache.size() - this.getMaxSize();
273     
274             for (Iterator it = list.iterator(); it.hasNext(); )
275             {
276                 if (count >= limit)
277                 {
278                     break;
279                 }
280     
281                 FileCacheEntry entry = (FileCacheEntry) it.next();
282                 String key = null;
283                 try
284                 {
285                     key = entry.getFile().getCanonicalPath();
286                 }                    
287                 catch (java.io.IOException e)
288                 {
289                     log.error("Exception getting file path: ", e);
290                 }
291                 // notify that eviction will soon take place
292                 for (Iterator lit = this.listeners.iterator(); lit.hasNext(); )
293                 {
294                     FileCacheEventListener listener = 
295                         (FileCacheEventListener) lit.next();
296                     try
297                     {
298                         listener.evict(entry);
299                     }
300                     catch (Exception e1)
301                     {
302                         log.warn("Unable to evict cache entry.  "+e1.toString(), e1);
303                     }                                    
304                 }
305                 cache.remove(key);
306     
307                 count++;
308             }        
309         }
310     }
311 
312     /***
313      * Evicts all entries
314      *
315      */
316     public void evictAll()        
317     {
318         synchronized (cache)
319         {
320             // evict all cache entries
321             List list = new LinkedList(cache.values());
322             for (Iterator it = list.iterator(); it.hasNext(); )
323             {
324                 // evict cache entry
325                 FileCacheEntry entry = (FileCacheEntry) it.next();
326                 // notify that eviction will soon take place
327                 for (Iterator lit = this.listeners.iterator(); lit.hasNext(); )
328                 {
329                     FileCacheEventListener listener = 
330                         (FileCacheEventListener) lit.next();
331                     try
332                     {
333                         listener.evict(entry);
334                     }
335                     catch (Exception e1)
336                     {
337                         log.warn("Unable to evict cache entry.  "+e1.toString(), e1);
338                     }                                    
339                 }
340                 // remove from cache by key
341                 String key = null;
342                 try
343                 {
344                     key = entry.getFile().getCanonicalPath();
345                 }                    
346                 catch (java.io.IOException e)
347                 {
348                     log.error("Exception getting file path: ", e);
349                 }
350                 cache.remove(key);
351             }        
352         }
353     }
354 
355     /***
356      * Comparator function for sorting by last accessed during eviction
357      *
358      */
359     public int compare(Object o1, Object o2)
360     {
361         FileCacheEntry e1 = (FileCacheEntry)o1;
362         FileCacheEntry e2 = (FileCacheEntry)o2;
363         if (e1.getLastAccessed() < e2.getLastAccessed())
364         {
365             return -1;
366         }
367         else if (e1.getLastAccessed() == e2.getLastAccessed())
368         {
369             return 0;
370         }
371         return 1;
372     }
373 
374     /***
375      * inner class that runs as a thread to scan the cache for updates or evictions
376      *
377      */
378     protected class FileCacheScanner extends Thread
379     {
380         private boolean stopping = false;
381 
382         public void setStopping(boolean flag)
383         {
384             this.stopping = flag;
385         }
386 
387         /***
388          * Run the file scanner thread
389          *
390          */
391         public void run()
392         {
393             boolean done = false;
394     
395             try
396             {
397                 while(!done)
398                 {
399                     try
400                     {
401                         int count = 0;
402                         Collection values = Collections.synchronizedCollection(FileCache.this.cache.values());
403                         synchronized (values)
404                         {
405                             for (Iterator it = values.iterator(); it.hasNext(); )
406                             {
407                                 FileCacheEntry entry = (FileCacheEntry) it.next();
408                                 Date modified = new Date(entry.getFile().lastModified());
409         
410                                 if (modified.after(entry.getLastModified()))
411                                 {                            
412                                     for (Iterator lit = FileCache.this.listeners.iterator(); lit.hasNext(); )
413                                     {
414                                         FileCacheEventListener listener = 
415                                             (FileCacheEventListener) lit.next();
416                                         try
417                                         {
418                                             listener.refresh(entry);
419                                         }
420                                         catch (Exception e1)
421                                         {
422                                             log.warn("Unable to refresh cached document:  "+e1.toString(), e1);
423                                         }                                    
424                                         entry.setLastModified(modified);
425                                     }
426                                 }
427                                 count++;
428                             }
429                         }
430                         if (count > FileCache.this.getMaxSize())
431                         {
432                             FileCache.this.evict();
433                         }
434                     }
435                     catch (Exception e)
436                     {
437                         log.error("FileCache Scanner: Error in iteration...", e);
438                     }
439     
440                     sleep(FileCache.this.getScanRate() * 1000);                
441 
442                     if (this.stopping)
443                     {
444                         this.stopping = false;
445                         done = true;
446                     }
447                 }
448             }
449             catch (InterruptedException e)
450             {
451                 log.error("FileCacheScanner: recieved interruption, exiting.", e);
452             }
453         }
454     } // end inner class:  FileCacheScanner
455 
456 
457     /***
458      * get an iterator over the cache values
459      *
460      * @return iterator over the cache values
461      */
462     public Iterator getIterator()
463     {
464         return cache.values().iterator();
465     }
466 
467     /***
468       * get the size of the cache
469       *
470       * @return the size of the cache
471       */
472     public int getSize()
473     {
474         return cache.size();
475     }
476 }
477