1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
47 protected int maxSize = 100;
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
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
321 List list = new LinkedList(cache.values());
322 for (Iterator it = list.iterator(); it.hasNext(); )
323 {
324
325 FileCacheEntry entry = (FileCacheEntry) it.next();
326
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
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 }
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