View Javadoc

1   /*
2    * Copyright 2000-2001,2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.jetspeed.cache;
18  
19  import java.util.Collections;
20  import java.util.Date;
21  import java.util.Map;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.LinkedList;
25  import java.util.Iterator;
26  import java.io.File;
27  
28  // Jetspeed logging classes
29  import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
30  import org.apache.jetspeed.services.logging.JetspeedLogger;
31  
32  /***
33   * FileCache keeps a cache of files up-to-date with a most simple eviction policy.
34   * The eviction policy will keep n items in the cache, and then start evicting
35   * the items ordered-by least used first. The cache runs a thread to check for
36   * both evictions and refreshes.
37   *
38   *  @author David S. Taylor <a href="mailto:taylor@apache.org">David Sean Taylor</a>
39   *  @version $Id: FileCache.java,v 1.5 2004/03/25 16:27:41 jford Exp $
40   */
41  
42  public class FileCache implements java.util.Comparator
43  {
44      protected long scanRate = 300;  // every 5 minutes
45      protected int maxSize = 100; // maximum of 100 items
46      protected List listeners = new LinkedList();
47  
48      private FileCacheScanner scanner = null;
49      private Map cache = null;
50      
51      /***
52       * Static initialization of the logger for this class
53       */    
54      private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(FileCache.class.getName()); 
55  
56      /***
57       * Default constructor. Use default values for scanReate and maxSize
58       *
59       */
60      public FileCache()
61      {
62          cache = new HashMap();
63          this.scanner = new FileCacheScanner();
64          this.scanner.setDaemon(true);
65      }
66  
67      /***
68       * Set scanRate and maxSize
69       *
70       * @param scanRate how often in seconds to refresh and evict from the cache
71       * @param maxSize the maximum allowed size of the cache before eviction starts
72       */
73      public FileCache(long scanRate, 
74                       int maxSize)
75      {
76          cache = 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 = 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         FileCacheEntry entry = new FileCacheEntry(file, document);
182         cache.put(file.getCanonicalPath(), entry);
183     }
184 
185     /***
186      * Puts a file entry in the file cache
187      *
188      * @param path the full path name of the file
189      * @param document the cached document
190      */
191     public void put(String key, Object document)
192         throws java.io.IOException
193     {
194         File file = new File(key);
195         FileCacheEntry entry = new FileCacheEntry(file, document);
196         cache.put(file.getCanonicalPath(), entry);
197     }
198 
199     /***
200      * Removes a file entry from the file cache
201      *
202      * @param key the full path name of the file
203      * @return the entry removed
204      */
205     public Object remove(String key)
206     {
207         return cache.remove(key);
208     }
209 
210 
211     /***
212      * Add a File Cache Event Listener 
213      *
214      * @param listener the event listener
215      */
216     public void addListener(FileCacheEventListener listener)
217     {
218         listeners.add(listener);
219     }
220 
221     /***
222      * Start the file Scanner running at the current scan rate.
223      *
224      */
225     public void startFileScanner()
226     {
227         try
228         {
229 
230             this.scanner.start();
231         }
232         catch (java.lang.IllegalThreadStateException e)
233         {
234             logger.error("Exception starting scanner", e);
235         }
236     }
237 
238     /***
239      * Stop the file Scanner 
240      *
241      */
242     public void stopFileScanner()
243     {
244         this.scanner.setStopping(true);
245     }
246 
247     /***
248      * Evicts entries based on last accessed time stamp
249      *
250      */
251     protected void evict()        
252     {
253         synchronized (cache)
254         {
255             if (this.getMaxSize() >= cache.size())
256             {
257                 return;
258             }
259     
260             List list = new LinkedList(cache.values());
261             Collections.sort(list, this);
262     
263             int count = 0;
264             int limit = cache.size() - this.getMaxSize();
265     
266             for (Iterator it = list.iterator(); it.hasNext(); )
267             {
268                 if (count >= limit)
269                 {
270                     break;
271                 }
272     
273                 FileCacheEntry entry = (FileCacheEntry) it.next();
274                 String key = null;
275                 try
276                 {
277                     key = entry.getFile().getCanonicalPath();
278                 }                    
279                 catch (java.io.IOException e)
280                 {
281                     logger.error("Exception getting file path: ", e);
282                 }
283                 // notify that eviction will soon take place
284                 for (Iterator lit = this.listeners.iterator(); lit.hasNext(); )
285                 {
286                     FileCacheEventListener listener = 
287                         (FileCacheEventListener) lit.next();
288                     listener.evict(entry);                                    
289                 }
290                 cache.remove(key);
291     
292                 count++;
293             }        
294         }
295     }
296 
297     /***
298      * Comparator function for sorting by last accessed during eviction
299      *
300      */
301     public int compare(Object o1, Object o2)
302     {
303         FileCacheEntry e1 = (FileCacheEntry)o1;
304         FileCacheEntry e2 = (FileCacheEntry)o2;
305         if (e1.getLastAccessed() < e2.getLastAccessed())
306         {
307             return -1;
308         }
309         else if (e1.getLastAccessed() == e2.getLastAccessed())
310         {
311             return 0;
312         }
313         return 1;
314     }
315 
316     /***
317      * inner class that runs as a thread to scan the cache for updates or evictions
318      *
319      */
320     protected class FileCacheScanner extends Thread
321     {
322         private boolean stopping = false;
323 
324         public void setStopping(boolean flag)
325         {
326             this.stopping = flag;
327         }
328 
329         /***
330          * Run the file scanner thread
331          *
332          */
333         public void run()
334         {
335             boolean done = false;
336     
337             try
338             {
339                 while(!done)
340                 {
341                     try
342                     {
343                         int count = 0;
344                         synchronized(FileCache.this)
345 						{
346 	                        for (Iterator it = FileCache.this.cache.values().iterator(); it.hasNext(); )
347 	                        {
348 	                            FileCacheEntry entry = (FileCacheEntry) it.next();
349 	                            Date modified = new Date(entry.getFile().lastModified());
350 	    
351 	                            if (modified.after(entry.getLastModified()))
352 	                            {                            
353 	                                for (Iterator lit = FileCache.this.listeners.iterator(); lit.hasNext(); )
354 	                                {
355 	                                    FileCacheEventListener listener = 
356 	                                        (FileCacheEventListener) lit.next();
357 	                                    listener.refresh(entry);                                    
358 	                                    entry.setLastModified(modified);
359 	                                }
360 	                            }
361 	                            count++;
362 	                        }
363 						}
364                         if (count > FileCache.this.getMaxSize())
365                         {
366                             FileCache.this.evict();
367                         }
368                     }
369                     catch (Exception e)
370                     {
371                         logger.error("FileCache Scanner: Error in iteration...", e);
372                     }
373     
374                     sleep(FileCache.this.getScanRate() * 1000);                
375 
376                     if (this.stopping)
377                     {
378                         this.stopping = false;
379                         done = true;
380                     }
381                 }
382             }
383             catch (InterruptedException e)
384             {
385                 logger.error("FileCacheScanner: recieved interruption, exiting.", e);
386             }
387         }
388     } // end inner class:  FileCacheScanner
389 
390 
391     /***
392      * get an iterator over the cache values
393      *
394      * @return iterator over the cache values
395      */
396     public Iterator getIterator()
397     {
398         return cache.values().iterator();
399     }
400 
401     /***
402       * get the size of the cache
403       *
404       * @return the size of the cache
405       */
406     public int getSize()
407     {
408         return cache.size();
409     }
410 }
411