1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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;
45 protected int maxSize = 100;
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
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 }
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