1 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 ; 13 import java.util.ArrayList ; 14 import java.util.Collections ; 15 import java.util.Iterator ; 16 import java.util.List ; 17 import java.util.Locale ; 18 import java.util.Map ; 19 import java.util.Set ; 20 import java.util.Timer ; 21 import java.util.TimerTask ; 22 import java.util.TreeMap ; 23 24 import javax.servlet.FilterChain ; 25 import javax.servlet.FilterConfig ; 26 import javax.servlet.ServletException ; 27 import javax.servlet.ServletRequest ; 28 import javax.servlet.ServletResponse ; 29 import javax.servlet.http.HttpServletRequest ; 30 import javax.servlet.http.HttpServletResponse ; 31 import org.roller.config.RollerConfig; 32 33 51 public class LRUCacheHandler implements FilterHandler 52 { 53 private static Log mLogger = 54 LogFactory.getFactory().getInstance(LRUCacheHandler.class); 55 56 private Map mPageCache = null; 57 58 private String mName = null; 59 60 61 private long mTimeoutInterval = 30 * 60 * 1000; 63 64 private float mTimeoutRatio = 1.0F; 65 66 private int misses = 0; 68 private int hits = 0; 69 70 private final static String 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 m_strBase64Chars = 74 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 75 76 77 public LRUCacheHandler(FilterConfig config) 78 { 79 mName = config.getFilterName(); 80 mLogger.info("Initializing for: " + mName); 81 82 String cacheSize = RollerConfig.getProperty("cache.filter.page.size"); 83 String cacheTimeout = RollerConfig.getProperty("cache.filter.page.timeout"); 84 85 int size = 200; 86 try 87 { 88 size = Integer.parseInt(cacheSize); 89 } 90 catch (Exception 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 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 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 timer = new Timer (); 138 timer.scheduleAtFixedRate(new TimerTask () { 139 public void run() { 140 timeoutCache(); 141 } 142 }, mTimeoutInterval, mTimeoutInterval); 143 } 144 } 145 146 149 public void destroy() 150 { 151 } 152 153 158 public void doFilter(ServletRequest req, ServletResponse res, 159 FilterChain chain) throws ServletException , IOException 160 { 161 HttpServletRequest request = (HttpServletRequest ) req; 162 HttpServletResponse response = (HttpServletResponse ) res; 163 164 Locale locale = LanguageUtil.getViewLocale(request); 166 167 String 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 java.security.Principal prince = request.getUserPrincipal(); 182 StringBuffer keyb = new StringBuffer (); 183 keyb.append(generatedKey); 184 if (prince != null) 185 { 186 keyb.append("_"); 187 keyb.append(prince); 188 } 189 String 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 if (request.getAttribute("DisplayException") == null) 203 { 204 ResponseContent rc = cacheResponse.getContent(); 205 putToCache(key, rc); 206 } 207 else 208 { 209 StringBuffer sb = new StringBuffer (); 210 sb.append("Display exception, cache, key="); 211 sb.append(key); 212 mLogger.error(sb.toString()); 213 } 214 } 215 catch (java.net.SocketException se) 216 { 217 } 219 catch (Exception e) 220 { 221 StringBuffer sb = new StringBuffer (); 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 se) 235 { 236 } 238 catch (Exception e) 239 { 240 if (mLogger.isDebugEnabled()) 241 { 242 StringBuffer sb = new StringBuffer (); 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 255 public synchronized void flushCache(HttpServletRequest req) 256 { 257 mPageCache.clear(); 258 } 259 260 263 public synchronized void removeFromCache(HttpServletRequest req, UserData user) 264 { 265 String rssString = "/rss/" + user.getUserName(); String pageString = "/page/" + user.getUserName(); String mainRssString = "/rss_"; List purgeList = new ArrayList (); 270 271 Iterator keys = mPageCache.keySet().iterator(); 272 while (keys.hasNext()) 273 { 274 String key = (String ) 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 purgeIter = purgeList.iterator(); 283 while (purgeIter.hasNext()) 284 { 285 String key = (String ) purgeIter.next(); 286 mPageCache.remove(key); 287 } 288 289 if (mLogger.isDebugEnabled()) 290 { 291 StringBuffer sb = new StringBuffer (); 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 allKeys = new ArrayList (mPageCache.keySet()); 314 for (int i=numToTimeout; i>0; i--) 315 { 316 mPageCache.remove(allKeys.get(i)); 317 } 318 } 319 } 320 321 325 public synchronized Object getFromCache(String key) 326 { 327 Object 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 key, Object entry) 337 { 338 mPageCache.put(key, entry); 339 if (mLogger.isDebugEnabled()) 340 { 341 misses++; 342 343 StringBuffer sb = new StringBuffer (); 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 generateEntryKey(String key, 357 HttpServletRequest request, int scope, String language) 358 { 359 StringBuffer cBuffer = new StringBuffer (AVERAGE_KEY_LENGTH); 360 if (language != null) 362 { 363 cBuffer.append(FILE_SEPARATOR).append(language); 364 } 365 366 368 if (key != null) 369 { 370 cBuffer.append(FILE_SEPARATOR).append(key); 371 } 372 else 373 { 374 String 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 digest = 387 java.security.MessageDigest.getInstance("MD5"); 388 byte[] b = digest.digest(generatedKey.getBytes()); 389 cBuffer.append("_"); 390 cBuffer.append(toBase64(b).replace('/', '_')); 392 } 393 catch (Exception e) 394 { 395 } 397 } 398 } 399 return cBuffer.toString(); 400 } 401 402 protected String getSortedQueryString(HttpServletRequest request) 403 { 404 Map paramMap = request.getParameterMap(); 405 if (paramMap.isEmpty()) 406 { 407 return null; 408 } 409 Set paramSet = new TreeMap (paramMap).entrySet(); 410 StringBuffer buf = new StringBuffer (); 411 boolean first = true; 412 for (Iterator it = paramSet.iterator(); it.hasNext();) 413 { 414 Map.Entry entry = (Map.Entry ) it.next(); 415 String [] values = (String []) entry.getValue(); 416 for (int i = 0; i < values.length; i++) 417 { 418 String key = (String ) 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 if (buf.length() == 0) 435 { 436 return null; 437 } 438 else 439 { 440 return buf.toString(); 441 } 442 } 443 444 447 private static String toBase64(byte[] aValue) 448 { 449 int byte1; 450 int byte2; 451 int byte3; 452 int iByteLen = aValue.length; 453 StringBuffer tt = new StringBuffer (); 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 |