001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.jetspeed.cache.file;
019:
020: import java.util.Collection;
021: import java.util.Collections;
022: import java.util.Date;
023: import java.util.Map;
024: import java.util.HashMap;
025: import java.util.List;
026: import java.util.LinkedList;
027: import java.util.Iterator;
028: import java.io.File;
029: import java.io.FileNotFoundException;
030:
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033:
034: /**
035: * FileCache keeps a cache of files up-to-date with a most simple eviction policy.
036: * The eviction policy will keep n items in the cache, and then start evicting
037: * the items ordered-by least used first. The cache runs a thread to check for
038: * both evictions and refreshes.
039: *
040: * @author David S. Taylor <a href="mailto:taylor@apache.org">David Sean Taylor</a>
041: * @version $Id: FileCache.java 516448 2007-03-09 16:25:47Z ate $
042: */
043:
044: public class FileCache implements java.util.Comparator {
045: protected long scanRate = 300; // every 5 minutes
046: protected int maxSize = 100; // maximum of 100 items
047: protected List listeners = new LinkedList();
048:
049: private FileCacheScanner scanner = null;
050: private Map cache = null;
051:
052: private final static Log log = LogFactory.getLog(FileCache.class);
053:
054: /**
055: * Default constructor. Use default values for scanReate and maxSize
056: *
057: */
058: public FileCache() {
059: cache = Collections.synchronizedMap(new HashMap());
060: this .scanner = new FileCacheScanner();
061: this .scanner.setDaemon(true);
062: }
063:
064: /**
065: * Set scanRate and maxSize
066: *
067: * @param scanRate how often in seconds to refresh and evict from the cache
068: * @param maxSize the maximum allowed size of the cache before eviction starts
069: */
070: public FileCache(long scanRate, int maxSize) {
071:
072: cache = Collections.synchronizedMap(new HashMap());
073:
074: this .scanRate = scanRate;
075: this .maxSize = maxSize;
076: this .scanner = new FileCacheScanner();
077: this .scanner.setDaemon(true);
078: }
079:
080: /**
081: * Set all parameters on the cache
082: *
083: * @param initialCapacity the initial size of the cache as passed to HashMap
084: * @param loadFactor how full the hash table is allowed to get before increasing
085: * @param scanRate how often in seconds to refresh and evict from the cache
086: * @param maxSize the maximum allowed size of the cache before eviction starts
087: */
088: public FileCache(int initialCapacity, int loadFactor,
089: long scanRate, int maxSize) {
090: cache = Collections.synchronizedMap(new HashMap(
091: initialCapacity, loadFactor));
092:
093: this .scanRate = scanRate;
094: this .maxSize = maxSize;
095: this .scanner = new FileCacheScanner();
096: this .scanner.setDaemon(true);
097: }
098:
099: /**
100: * Set the new refresh scan rate on managed files.
101: *
102: * @param scanRate the new scan rate in seconds
103: */
104: public void setScanRate(long scanRate) {
105: this .scanRate = scanRate;
106: }
107:
108: /**
109: * Get the refresh scan rate
110: *
111: * @return the current refresh scan rate in seconds
112: */
113: public long getScanRate() {
114: return scanRate;
115: }
116:
117: /**
118: * Set the new maximum size of the cache
119: *
120: * @param maxSize the maximum size of the cache
121: */
122: public void setMaxSize(int maxSize) {
123: this .maxSize = maxSize;
124: }
125:
126: /**
127: * Get the maximum size of the cache
128: *
129: * @return the current maximum size of the cache
130: */
131: public int getMaxSize() {
132: return maxSize;
133: }
134:
135: /**
136: * Gets an entry from the cache given a key
137: *
138: * @param key the key to look up the entry by
139: * @return the entry
140: */
141: public FileCacheEntry get(String key) {
142: return (FileCacheEntry) cache.get(key);
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 Object getDocument(String key) {
152: FileCacheEntry entry = (FileCacheEntry) cache.get(key);
153: if (entry != null) {
154: return entry.getDocument();
155: }
156: return null;
157: }
158:
159: /**
160: * Puts a file entry in the file cache
161: *
162: * @param file The file to be put in the cache
163: * @param document the cached document
164: */
165: public void put(File file, Object document)
166: throws java.io.IOException {
167: if (!file.exists()) {
168: throw new FileNotFoundException("File to cache: "
169: + file.getAbsolutePath() + " does not exist.");
170: }
171: FileCacheEntry entry = new FileCacheEntryImpl(file, document);
172: cache.put(file.getCanonicalPath(), entry);
173: }
174:
175: /**
176: * Puts a file entry in the file cache
177: *
178: * @param path the full path name of the file
179: * @param document the cached document
180: */
181: public void put(String key, Object document, File rootFile)
182: throws java.io.IOException {
183: File file = new File(rootFile, key);
184: if (!file.exists()) {
185: throw new FileNotFoundException("File to cache: "
186: + file.getAbsolutePath() + " does not exist.");
187: }
188: FileCacheEntry entry = new FileCacheEntryImpl(file, document);
189: cache.put(key, entry);
190: }
191:
192: /**
193: * Removes a file entry from the file cache
194: *
195: * @param key the full path name of the file
196: * @return the entry removed
197: */
198: public Object remove(String key) {
199: return cache.remove(key);
200: }
201:
202: /**
203: * Add a File Cache Event Listener
204: *
205: * @param listener the event listener
206: */
207: public void addListener(FileCacheEventListener listener) {
208: listeners.add(listener);
209: }
210:
211: /**
212: * Start the file Scanner running at the current scan rate.
213: *
214: */
215: public void startFileScanner() {
216: try {
217:
218: this .scanner.start();
219: } catch (java.lang.IllegalThreadStateException e) {
220: log.error("Exception starting scanner", e);
221: }
222: }
223:
224: /**
225: * Stop the file Scanner
226: *
227: */
228: public void stopFileScanner() {
229: this .scanner.setStopping(true);
230: }
231:
232: /**
233: * Evicts entries based on last accessed time stamp
234: *
235: */
236: protected void evict() {
237: synchronized (cache) {
238: if (this .getMaxSize() >= cache.size()) {
239: return;
240: }
241:
242: List list = new LinkedList(cache.values());
243: Collections.sort(list, this );
244:
245: int count = 0;
246: int limit = cache.size() - this .getMaxSize();
247:
248: for (Iterator it = list.iterator(); it.hasNext();) {
249: if (count >= limit) {
250: break;
251: }
252:
253: FileCacheEntry entry = (FileCacheEntry) it.next();
254: String key = null;
255: try {
256: key = entry.getFile().getCanonicalPath();
257: } catch (java.io.IOException e) {
258: log.error("Exception getting file path: ", e);
259: }
260: // notify that eviction will soon take place
261: for (Iterator lit = this .listeners.iterator(); lit
262: .hasNext();) {
263: FileCacheEventListener listener = (FileCacheEventListener) lit
264: .next();
265: try {
266: listener.evict(entry);
267: } catch (Exception e1) {
268: log.warn("Unable to evict cache entry. "
269: + e1.toString(), e1);
270: }
271: }
272: cache.remove(key);
273:
274: count++;
275: }
276: }
277: }
278:
279: /**
280: * Evicts all entries
281: *
282: */
283: public void evictAll() {
284: synchronized (cache) {
285: // evict all cache entries
286: List list = new LinkedList(cache.values());
287: for (Iterator it = list.iterator(); it.hasNext();) {
288: // evict cache entry
289: FileCacheEntry entry = (FileCacheEntry) it.next();
290: // notify that eviction will soon take place
291: for (Iterator lit = this .listeners.iterator(); lit
292: .hasNext();) {
293: FileCacheEventListener listener = (FileCacheEventListener) lit
294: .next();
295: try {
296: listener.evict(entry);
297: } catch (Exception e1) {
298: log.warn("Unable to evict cache entry. "
299: + e1.toString(), e1);
300: }
301: }
302: // remove from cache by key
303: String key = null;
304: try {
305: key = entry.getFile().getCanonicalPath();
306: } catch (java.io.IOException e) {
307: log.error("Exception getting file path: ", e);
308: }
309: cache.remove(key);
310: }
311: }
312: }
313:
314: /**
315: * Comparator function for sorting by last accessed during eviction
316: *
317: */
318: public int compare(Object o1, Object o2) {
319: FileCacheEntry e1 = (FileCacheEntry) o1;
320: FileCacheEntry e2 = (FileCacheEntry) o2;
321: if (e1.getLastAccessed() < e2.getLastAccessed()) {
322: return -1;
323: } else if (e1.getLastAccessed() == e2.getLastAccessed()) {
324: return 0;
325: }
326: return 1;
327: }
328:
329: /**
330: * inner class that runs as a thread to scan the cache for updates or evictions
331: *
332: */
333: protected class FileCacheScanner extends Thread {
334: private boolean stopping = false;
335:
336: public void setStopping(boolean flag) {
337: this .stopping = flag;
338: }
339:
340: /**
341: * Run the file scanner thread
342: *
343: */
344: public void run() {
345: boolean done = false;
346:
347: try {
348: while (!done) {
349: try {
350: int count = 0;
351: Collection values = Collections
352: .synchronizedCollection(FileCache.this .cache
353: .values());
354: synchronized (values) {
355: for (Iterator it = values.iterator(); it
356: .hasNext();) {
357: FileCacheEntry entry = (FileCacheEntry) it
358: .next();
359: Date modified = new Date(entry
360: .getFile().lastModified());
361:
362: if (modified.after(entry
363: .getLastModified())) {
364: for (Iterator lit = FileCache.this .listeners
365: .iterator(); lit.hasNext();) {
366: FileCacheEventListener listener = (FileCacheEventListener) lit
367: .next();
368: try {
369: listener.refresh(entry);
370: } catch (Exception e1) {
371: log
372: .warn(
373: "Unable to refresh cached document: "
374: + e1
375: .toString(),
376: e1);
377: }
378: entry.setLastModified(modified);
379: }
380: }
381: count++;
382: }
383: }
384: if (count > FileCache.this .getMaxSize()) {
385: FileCache.this .evict();
386: }
387: } catch (Exception e) {
388: log
389: .error(
390: "FileCache Scanner: Error in iteration...",
391: e);
392: }
393:
394: sleep(FileCache.this .getScanRate() * 1000);
395:
396: if (this .stopping) {
397: this .stopping = false;
398: done = true;
399: }
400: }
401: } catch (InterruptedException e) {
402: log
403: .error(
404: "FileCacheScanner: recieved interruption, exiting.",
405: e);
406: }
407: }
408: } // end inner class: FileCacheScanner
409:
410: /**
411: * get an iterator over the cache values
412: *
413: * @return iterator over the cache values
414: */
415: public Iterator getIterator() {
416: return cache.values().iterator();
417: }
418:
419: /**
420: * get the size of the cache
421: *
422: * @return the size of the cache
423: */
424: public int getSize() {
425: return cache.size();
426: }
427: }
|