KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > roller > presentation > pagecache > rollercache > LRUCacheHandler


1 /*
2  * Created on Jun 15, 2004
3  */

4 package org.roller.presentation.pagecache.rollercache;
5 import org.apache.commons.logging.Log;
6 import org.apache.commons.logging.LogFactory;
7 import org.roller.pojos.UserData;
8 import org.roller.presentation.LanguageUtil;
9 import org.roller.presentation.pagecache.FilterHandler;
10 import org.roller.util.LRUCache;
11
12 import java.io.IOException JavaDoc;
13 import java.util.ArrayList JavaDoc;
14 import java.util.Collections JavaDoc;
15 import java.util.Iterator JavaDoc;
16 import java.util.List JavaDoc;
17 import java.util.Locale JavaDoc;
18 import java.util.Map JavaDoc;
19 import java.util.Set JavaDoc;
20 import java.util.Timer JavaDoc;
21 import java.util.TimerTask JavaDoc;
22 import java.util.TreeMap JavaDoc;
23
24 import javax.servlet.FilterChain JavaDoc;
25 import javax.servlet.FilterConfig JavaDoc;
26 import javax.servlet.ServletException JavaDoc;
27 import javax.servlet.ServletRequest JavaDoc;
28 import javax.servlet.ServletResponse JavaDoc;
29 import javax.servlet.http.HttpServletRequest JavaDoc;
30 import javax.servlet.http.HttpServletResponse JavaDoc;
31 import org.roller.config.RollerConfig;
32
33 /**
34  * Page cache implementation that uses a simple LRUCache. Can be configured
35  * using filter configuration parameters:
36  * <ul>
37  * <li><b>size</b>: number of pages to keep in cache. Once cache reaches
38  * this size, each new cache entry will push out the LRU cache entry.</li>
39  *
40  * <li><b>timeoutInterval</b>: interval to timeout pages in seconds
41  * (default is 1800 seconds). Sites with a large number of users, and thus a
42  * lot of cache churn which makes this check unnecessary, may wish to set this
43  * to -1 to disable timeout checking.</li>
44  *
45  * <li><b>timeoutRatio</b>: portion of old pages to expire on timeout
46  * interval where 1.0 is 100% (default is 1.0). This only applies if the
47  * timeoutInterval is set to something other than -1.</li>
48  * </ul>
49  * @author David M Johnson
50  */

51 public class LRUCacheHandler implements FilterHandler
52 {
53     private static Log mLogger =
54         LogFactory.getFactory().getInstance(LRUCacheHandler.class);
55
56     private Map JavaDoc mPageCache = null;
57     
58     private String JavaDoc mName = null;
59     
60     /** Timeout interval: how often to run timeout task (default 30 mintes) */
61     private long mTimeoutInterval = 30 * 60 * 1000; // milliseconds
62

63     /** Timeout ratio: % of cache to expire on each timeout (default 1.0) */
64     private float mTimeoutRatio = 1.0F;
65     
66     // Statistics
67
private int misses = 0;
68     private int hits = 0;
69     
70     private final static String JavaDoc FILE_SEPARATOR = "/";
71     private final static char FILE_SEPARATOR_CHAR = FILE_SEPARATOR.charAt(0);
72     private final static short AVERAGE_KEY_LENGTH = 30;
73     private static final String JavaDoc m_strBase64Chars =
74         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
75     
76
77     public LRUCacheHandler(FilterConfig JavaDoc config)
78     {
79         mName = config.getFilterName();
80         mLogger.info("Initializing for: " + mName);
81         
82         String JavaDoc cacheSize = RollerConfig.getProperty("cache.filter.page.size");
83         String JavaDoc cacheTimeout = RollerConfig.getProperty("cache.filter.page.timeout");
84         
85         int size = 200;
86         try
87         {
88             size = Integer.parseInt(cacheSize);
89         }
90         catch (Exception JavaDoc e)
91         {
92             mLogger.warn(config.getFilterName()
93                 + "Can't read cache size parameter, using default...");
94         }
95         mLogger.info(mName + " size=" + size);
96         mPageCache = Collections.synchronizedMap(new LRUCache(size));
97
98         long intervalSeconds = mTimeoutInterval / 1000L;
99         try
100         {
101             mTimeoutInterval = 1000L * Long.parseLong(cacheTimeout);
102             
103             if (mTimeoutInterval == -1)
104             {
105                 mLogger.info(config.getFilterName()
106                    + "timeoutInterval of -1: timeouts are disabled");
107             }
108             else if (mTimeoutInterval < (30 * 1000))
109             {
110                 mTimeoutInterval = 30 * 1000;
111                 mLogger.warn(config.getFilterName()
112                    + "timeoutInterval cannot be less than 30 seconds");
113             }
114         }
115         catch (Exception JavaDoc e)
116         {
117             mLogger.warn(config.getFilterName()
118                 + "Can't read timeoutInterval parameter, disabling timeout.");
119             mTimeoutInterval = -1;
120         }
121         mLogger.info(mName + " timeoutInterval=" + intervalSeconds);
122         
123         try
124         {
125             mTimeoutRatio = Float.parseFloat(
126                 config.getInitParameter("timeoutRatio"));
127         }
128         catch (Exception JavaDoc e)
129         {
130             mLogger.warn(config.getFilterName()
131                 + "Can't read timeoutRatio parameter, using default...");
132         }
133         mLogger.info(mName + " timeoutRatio=" + mTimeoutRatio);
134         
135         if (mTimeoutInterval != -1 && mTimeoutRatio != 0.0)
136         {
137             Timer JavaDoc timer = new Timer JavaDoc();
138             timer.scheduleAtFixedRate(new TimerTask JavaDoc() {
139                 public void run() {
140                     timeoutCache();
141                 }
142             }, mTimeoutInterval, mTimeoutInterval);
143         }
144     }
145     
146     /**
147      * @see org.roller.presentation.pagecache.FilterHandler#destroy()
148      */

149     public void destroy()
150     {
151     }
152
153     /**
154      * @see org.roller.presentation.pagecache.FilterHandler#doFilter(
155      * javax.servlet.ServletRequest, javax.servlet.ServletResponse,
156      * javax.servlet.FilterChain)
157      */

158     public void doFilter(ServletRequest JavaDoc req, ServletResponse JavaDoc res,
159                     FilterChain JavaDoc chain) throws ServletException JavaDoc, IOException JavaDoc
160     {
161         HttpServletRequest JavaDoc request = (HttpServletRequest JavaDoc) req;
162         HttpServletResponse JavaDoc response = (HttpServletResponse JavaDoc) res;
163
164         // get locale
165
Locale JavaDoc locale = LanguageUtil.getViewLocale(request);
166         
167         // generate language-sensitive cache-key
168
String JavaDoc generatedKey = null;
169         if (locale != null)
170         {
171             generatedKey = generateEntryKey(null,
172                       request, 1, locale.getLanguage());
173         }
174         else
175         {
176             generatedKey = generateEntryKey(null,
177                       request, 1, null);
178         }
179         
180         // Add authenticated user name, if there is one, to cache key
181
java.security.Principal JavaDoc prince = request.getUserPrincipal();
182         StringBuffer JavaDoc keyb = new StringBuffer JavaDoc();
183         keyb.append(generatedKey);
184         if (prince != null)
185         {
186             keyb.append("_");
187             keyb.append(prince);
188         }
189         String JavaDoc key = keyb.toString();
190                 
191         ResponseContent respContent = (ResponseContent)getFromCache(key);
192         if (respContent == null)
193         {
194             try
195             {
196                 CacheHttpServletResponseWrapper cacheResponse =
197                     new CacheHttpServletResponseWrapper(response);
198                 chain.doFilter(request, cacheResponse);
199                 
200                 // Store as the cache content the result of the response
201
// if no exception was noted by content generator.
202
if (request.getAttribute("DisplayException") == null)
203                 {
204                     ResponseContent rc = cacheResponse.getContent();
205                     putToCache(key, rc);
206                 }
207                 else
208                 {
209                     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
210                     sb.append("Display exception, cache, key=");
211                     sb.append(key);
212                     mLogger.error(sb.toString());
213                 }
214             }
215             catch (java.net.SocketException JavaDoc se)
216             {
217                 // ignore socket exceptions
218
}
219             catch (Exception JavaDoc e)
220             {
221                 // something unexpected and bad happened
222
StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
223                 sb.append("Error rendering page, key=");
224                 sb.append(key);
225                 mLogger.error(sb.toString());
226             }
227         }
228         else
229         {
230             try
231             {
232                 respContent.writeTo(response);
233             }
234             catch (java.net.SocketException JavaDoc se)
235             {
236                 // ignore socket exceptions
237
}
238             catch (Exception JavaDoc e)
239             {
240                 if (mLogger.isDebugEnabled())
241                 {
242                     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
243                     sb.append("Probably a client abort exception, key=");
244                     sb.append(key);
245                     mLogger.error(sb.toString());
246                 }
247             }
248             
249         }
250     }
251
252     /**
253      * Purge entire cache.
254      */

255     public synchronized void flushCache(HttpServletRequest JavaDoc req)
256     {
257         mPageCache.clear();
258     }
259
260     /**
261      * Purge user's entries from cache.
262      */

263     public synchronized void removeFromCache(HttpServletRequest JavaDoc req, UserData user)
264     {
265         // TODO: can we make this a little more precise, perhaps via regex?
266
String JavaDoc rssString = "/rss/" + user.getUserName(); // user's pages
267
String JavaDoc pageString = "/page/" + user.getUserName(); // user's RSS feeds
268
String JavaDoc mainRssString = "/rss_"; // main RSS feed
269
List JavaDoc purgeList = new ArrayList JavaDoc();
270         
271         Iterator JavaDoc keys = mPageCache.keySet().iterator();
272         while (keys.hasNext())
273         {
274             String JavaDoc key = (String JavaDoc) keys.next();
275             
276             if (key.indexOf(rssString)!=-1 || key.indexOf(pageString)!=-1 || key.indexOf(mainRssString)!=-1)
277             {
278                 purgeList.add(key);
279             }
280         }
281         
282         Iterator JavaDoc purgeIter = purgeList.iterator();
283         while (purgeIter.hasNext())
284         {
285             String JavaDoc key = (String JavaDoc) purgeIter.next();
286             mPageCache.remove(key);
287         }
288         
289         if (mLogger.isDebugEnabled())
290         {
291             StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
292             sb.append("Purged, count=");
293             sb.append(purgeList.size());
294             sb.append(", user=");
295             sb.append(user.getUserName());
296             mLogger.debug(sb.toString());
297         }
298     }
299     
300     public synchronized void timeoutCache()
301     {
302         if (mTimeoutRatio == 1.0)
303         {
304             mLogger.debug("Timing out whole cache: " + mName);
305             mPageCache.clear();
306         }
307         else
308         {
309             int numToTimeout = (int)(mTimeoutRatio * mPageCache.size());
310             mLogger.debug(
311                 "Timing out " + numToTimeout + " of " + mPageCache.size()
312                 + " entries from cache: " + mName);
313             ArrayList JavaDoc allKeys = new ArrayList JavaDoc(mPageCache.keySet());
314             for (int i=numToTimeout; i>0; i--)
315             {
316                 mPageCache.remove(allKeys.get(i));
317             }
318         }
319     }
320     
321     /**
322      * Get from cache. Synchronized because "In access-ordered linked hash
323      * maps, merely querying the map with get is a structural modification"
324      */

325     public synchronized Object JavaDoc getFromCache(String JavaDoc key)
326     {
327         Object JavaDoc entry = mPageCache.get(key);
328         
329         if (entry != null && mLogger.isDebugEnabled())
330         {
331             hits++;
332         }
333         return entry;
334     }
335
336     public synchronized void putToCache(String JavaDoc key, Object JavaDoc entry)
337     {
338         mPageCache.put(key, entry);
339         if (mLogger.isDebugEnabled())
340         {
341             misses++;
342             
343             StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
344             sb.append("Missed, cache size=");
345             sb.append(mPageCache.size());
346             sb.append(", hits=");
347             sb.append(hits);
348             sb.append(", misses=");
349             sb.append(misses);
350             sb.append(", key=");
351             sb.append(key);
352             mLogger.debug(sb.toString());
353         }
354     }
355
356     public String JavaDoc generateEntryKey(String JavaDoc key,
357                        HttpServletRequest JavaDoc request, int scope, String JavaDoc language)
358     {
359         StringBuffer JavaDoc cBuffer = new StringBuffer JavaDoc(AVERAGE_KEY_LENGTH);
360         // Append the language if available
361
if (language != null)
362         {
363             cBuffer.append(FILE_SEPARATOR).append(language);
364         }
365         
366         //cBuffer.append(FILE_SEPARATOR).append(request.getServerName());
367

368         if (key != null)
369         {
370             cBuffer.append(FILE_SEPARATOR).append(key);
371         }
372         else
373         {
374             String JavaDoc generatedKey = request.getRequestURI();
375             if (generatedKey.charAt(0) != FILE_SEPARATOR_CHAR)
376             {
377                 cBuffer.append(FILE_SEPARATOR_CHAR);
378             }
379             cBuffer.append(generatedKey);
380             cBuffer.append("_").append(request.getMethod()).append("_");
381             generatedKey = getSortedQueryString(request);
382             if (generatedKey != null)
383             {
384                 try
385                 {
386                     java.security.MessageDigest JavaDoc digest =
387                         java.security.MessageDigest.getInstance("MD5");
388                     byte[] b = digest.digest(generatedKey.getBytes());
389                     cBuffer.append("_");
390                     // Base64 encoding allows for unwanted slash characters.
391
cBuffer.append(toBase64(b).replace('/', '_'));
392                 }
393                 catch (Exception JavaDoc e)
394                 {
395                     // Ignore query string
396
}
397             }
398         }
399         return cBuffer.toString();
400     }
401
402     protected String JavaDoc getSortedQueryString(HttpServletRequest JavaDoc request)
403     {
404         Map JavaDoc paramMap = request.getParameterMap();
405         if (paramMap.isEmpty())
406         {
407             return null;
408         }
409         Set JavaDoc paramSet = new TreeMap JavaDoc(paramMap).entrySet();
410         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
411         boolean first = true;
412         for (Iterator JavaDoc it = paramSet.iterator(); it.hasNext();)
413         {
414             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
415             String JavaDoc[] values = (String JavaDoc[]) entry.getValue();
416             for (int i = 0; i < values.length; i++)
417             {
418                 String JavaDoc key = (String JavaDoc) entry.getKey();
419                 if ((key.length() != 10) || !"jsessionid".equals(key))
420                 {
421                     if (first)
422                     {
423                         first = false;
424                     }
425                     else
426                     {
427                         buf.append('&');
428                     }
429                     buf.append(key).append('=').append(values[i]);
430                 }
431             }
432         }
433         // We get a 0 length buffer if the only parameter was a jsessionid
434
if (buf.length() == 0)
435         {
436             return null;
437         }
438         else
439         {
440             return buf.toString();
441         }
442     }
443
444     /**
445      * Convert a byte array into a Base64 string (as used in mime formats)
446      */

447     private static String JavaDoc toBase64(byte[] aValue)
448     {
449         int byte1;
450         int byte2;
451         int byte3;
452         int iByteLen = aValue.length;
453         StringBuffer JavaDoc tt = new StringBuffer JavaDoc();
454         for (int i = 0; i < iByteLen; i += 3)
455         {
456             boolean bByte2 = (i + 1) < iByteLen;
457             boolean bByte3 = (i + 2) < iByteLen;
458             byte1 = aValue[i] & 0xFF;
459             byte2 = (bByte2) ? (aValue[i + 1] & 0xFF) : 0;
460             byte3 = (bByte3) ? (aValue[i + 2] & 0xFF) : 0;
461             tt.append(m_strBase64Chars.charAt(byte1 / 4));
462             tt.append(m_strBase64Chars.charAt((byte2 / 16)
463                             + ((byte1 & 0x3) * 16)));
464             tt.append(((bByte2) ? m_strBase64Chars.charAt((byte3 / 64)
465                             + ((byte2 & 0xF) * 4)) : '='));
466             tt.append(((bByte3) ? m_strBase64Chars.charAt(byte3 & 0x3F) : '='));
467         }
468         return tt.toString();
469     }
470 }
471
Popular Tags