Clover coverage report -
Coverage timestamp: So Nov 6 2005 14:19:51 CET
file stats: LOC: 459   Methods: 10
NCLOC: 248   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
CacheFilter.java 0% 0% 0% 0%
coverage
 1    /*
 2    * Copyright (c) 2002-2003 by OpenSymphony
 3    * All rights reserved.
 4    */
 5    package com.opensymphony.oscache.web.filter;
 6   
 7    import com.opensymphony.oscache.base.Cache;
 8    import com.opensymphony.oscache.base.NeedsRefreshException;
 9    import com.opensymphony.oscache.web.ServletCacheAdministrator;
 10   
 11    import org.apache.commons.logging.Log;
 12    import org.apache.commons.logging.LogFactory;
 13   
 14    import java.io.IOException;
 15   
 16    import javax.servlet.*;
 17    import javax.servlet.http.HttpServletRequest;
 18    import javax.servlet.http.HttpServletResponse;
 19    import javax.servlet.jsp.PageContext;
 20   
 21    /**
 22    * CacheFilter is a filter that allows for server-side caching of post-processed servlet content.<p>
 23    *
 24    * It also gives great programatic control over refreshing, flushing and updating the cache.<p>
 25    *
 26    * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
 27    * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
 28    * @author <a href="mailto:ltorunski@t-online.de">Lars Torunski</a>
 29    * @version $Revision: 1.9 $
 30    */
 31    public class CacheFilter implements Filter, ICacheKeyProvider, ICacheGroupsProvider {
 32    // Header
 33    public static final String HEADER_LAST_MODIFIED = "Last-Modified";
 34    public static final String HEADER_CONTENT_TYPE = "Content-Type";
 35    public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
 36    public static final String HEADER_EXPIRES = "Expires";
 37    public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
 38    public static final String HEADER_CACHE_CONTROL = "Cache-control";
 39    public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
 40   
 41    // Fragment parameter
 42    public static final int FRAGMENT_AUTODETECT = -1;
 43    public static final int FRAGMENT_NO = 0;
 44    public static final int FRAGMENT_YES = 1;
 45   
 46    // No cache parameter
 47    public static final int NOCACHE_OFF = 0;
 48    public static final int NOCACHE_SESSION_ID_IN_URL = 1;
 49   
 50    // Last Modified parameter
 51    public static final long LAST_MODIFIED_OFF = 0;
 52    public static final long LAST_MODIFIED_ON = 1;
 53    public static final long LAST_MODIFIED_INITIAL = -1;
 54   
 55    // Expires parameter
 56    public static final long EXPIRES_OFF = 0;
 57    public static final long EXPIRES_ON = 1;
 58    public static final long EXPIRES_TIME = -1;
 59   
 60    // request attribute to avoid reentrance
 61    private final static String REQUEST_FILTERED = "__oscache_filtered";
 62   
 63    // the policy for the expires header
 64    private ExpiresRefreshPolicy expiresRefreshPolicy;
 65   
 66    // the logger
 67    private final Log log = LogFactory.getLog(this.getClass());
 68   
 69    // filter variables
 70    private FilterConfig config;
 71    private ServletCacheAdministrator admin = null;
 72    private int cacheScope = PageContext.APPLICATION_SCOPE; // filter scope - default is APPLICATION
 73    private int fragment = FRAGMENT_AUTODETECT; // defines if this filter handles fragments of a page - default is auto detect
 74    private int time = 60 * 60; // time before cache should be refreshed - default one hour (in seconds)
 75    private int nocache = NOCACHE_OFF; // defines special no cache option for the requests - default is off
 76    private long lastModified = LAST_MODIFIED_INITIAL; // defines if the last-modified-header will be sent - default is intial setting
 77    private long expires = EXPIRES_ON; // defines if the expires-header will be sent - default is on
 78    private ICacheKeyProvider cacheKeyProvider = this; // the provider of the cache key - default is the CacheFilter itselfs
 79    private ICacheGroupsProvider cacheGroupsProvider = this; // the provider of the cache groups - default is the CacheFilter itselfs
 80   
 81    /**
 82    * Filter clean-up
 83    */
 84  0 public void destroy() {
 85    //Not much to do...
 86    }
 87   
 88    /**
 89    * The doFilter call caches the response by wrapping the <code>HttpServletResponse</code>
 90    * object so that the output stream can be caught. This works by splitting off the output
 91    * stream into two with the {@link SplitServletOutputStream} class. One stream gets written
 92    * out to the response as normal, the other is fed into a byte array inside a {@link ResponseContent}
 93    * object.
 94    *
 95    * @param request The servlet request
 96    * @param response The servlet response
 97    * @param chain The filter chain
 98    * @throws ServletException IOException
 99    */
 100  0 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
 101  0 if (log.isInfoEnabled()) {
 102  0 log.info("<cache>: filter in scope " + cacheScope);
 103    }
 104   
 105    // avoid reentrance (CACHE-128) and check if request is cacheable
 106  0 if (isFilteredBefore(request) || !isCacheable(request)) {
 107  0 chain.doFilter(request, response);
 108  0 return;
 109    }
 110  0 request.setAttribute(REQUEST_FILTERED, Boolean.TRUE);
 111   
 112  0 HttpServletRequest httpRequest = (HttpServletRequest) request;
 113   
 114    // checks if the response will be a fragment of a page
 115  0 boolean fragmentRequest = isFragment(httpRequest);
 116   
 117    // avoid useless session creation for application scope pages (CACHE-129)
 118  0 Cache cache;
 119  0 if (cacheScope == PageContext.SESSION_SCOPE) {
 120  0 cache = admin.getSessionScopeCache(httpRequest.getSession(true));
 121    } else {
 122  0 cache = admin.getAppScopeCache(config.getServletContext());
 123    }
 124   
 125    // generate the cache entry key
 126  0 String key = cacheKeyProvider.createCacheKey(httpRequest, admin, cache);
 127   
 128  0 try {
 129  0 ResponseContent respContent = (ResponseContent) cache.getFromCache(key, time);
 130   
 131  0 if (log.isInfoEnabled()) {
 132  0 log.info("<cache>: Using cached entry for " + key);
 133    }
 134   
 135  0 boolean acceptsGZip = false;
 136  0 if ((!fragmentRequest) && (lastModified != LAST_MODIFIED_OFF)) {
 137  0 long clientLastModified = httpRequest.getDateHeader(HEADER_IF_MODIFIED_SINCE); // will return -1 if no header...
 138   
 139    // only reply with SC_NOT_MODIFIED
 140    // if the client has already the newest page and the response isn't a fragment in a page
 141  0 if ((clientLastModified != -1) && (clientLastModified >= respContent.getLastModified())) {
 142  0 ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
 143  0 return;
 144    }
 145   
 146  0 acceptsGZip = respContent.isContentGZiped() && acceptsGZipEncoding(httpRequest);
 147    }
 148   
 149  0 respContent.writeTo(response, fragmentRequest, acceptsGZip);
 150    // acceptsGZip is used for performance reasons above; use the following line for CACHE-49
 151    // respContent.writeTo(response, fragmentRequest, acceptsGZipEncoding(httpRequest));
 152    } catch (NeedsRefreshException nre) {
 153  0 boolean updateSucceeded = false;
 154   
 155  0 try {
 156  0 if (log.isInfoEnabled()) {
 157  0 log.info("<cache>: New cache entry, cache stale or cache scope flushed for " + key);
 158    }
 159   
 160  0 CacheHttpServletResponseWrapper cacheResponse = new CacheHttpServletResponseWrapper((HttpServletResponse) response, fragmentRequest, time * 1000L, lastModified, expires);
 161  0 chain.doFilter(request, cacheResponse);
 162  0 cacheResponse.flushBuffer();
 163   
 164    // Only cache if the response is cacheable
 165  0 if (isCacheable(cacheResponse)) {
 166    // get the cache groups of the content
 167  0 String[] groups = cacheGroupsProvider.createCacheGroups(httpRequest, admin, cache);
 168    // Store as the cache content the result of the response
 169  0 cache.putInCache(key, cacheResponse.getContent(), groups, expiresRefreshPolicy, null);
 170  0 updateSucceeded = true;
 171    }
 172    } finally {
 173  0 if (!updateSucceeded) {
 174  0 cache.cancelUpdate(key);
 175    }
 176    }
 177    }
 178    }
 179   
 180    /**
 181    * Initialize the filter. This retrieves a {@link ServletCacheAdministrator}
 182    * instance and configures the filter based on any initialization parameters.<p>
 183    * The supported initialization parameters are:
 184    * <ul>
 185    *
 186    * <li><b>time</b> - the default time (in seconds) to cache content for. The default
 187    * value is 3600 seconds (one hour).</li>
 188    *
 189    * <li><b>scope</b> - the default scope to cache content in. Acceptable values
 190    * are <code>application</code> (default), <code>session</code>, <code>request</code> and
 191    * <code>page</code>.</li>
 192    *
 193    * <li><b>fragment</b> - defines if this filter handles fragments of a page. Acceptable values
 194    * are <code>auto</code> (default) for auto detect, <code>no</code> and <code>yes</code>.</li>
 195    *
 196    * <li><b>nocache</b> - defines which objects shouldn't be cached. Acceptable values
 197    * are <code>off</code> (default) and <code>sessionIdInURL</code> if the session id is
 198    * contained in the URL.</li>
 199    *
 200    * <li><b>lastModified</b> - defines if the last modified header will be sent in the response. Acceptable values are
 201    * <code>off</code> for don't sending the header, even it is set in the filter chain,
 202    * <code>on</code> for sending it if it is set in the filter chain and
 203    * <code>inital</code> (default) the last modified information will be set based on the current time and changes are allowed.</li>
 204    *
 205    * <li><b>expires</b> - defines if the expires header will be sent in the response. Acceptable values are
 206    * <code>off</code> for don't sending the header, even it is set in the filter chain,
 207    * <code>on</code> (default) for sending it if it is set in the filter chain and
 208    * <code>time</code> the expires information will be intialized based on the time parameter and creation time of the content.</li>
 209    *
 210    * <li><b>ICacheKeyProvider</b> - Class implementing the interface <code>ICacheKeyProvider</code>.
 211    * A developer can implement a method which provides cache keys based on the request,
 212    * the servlect cache administrator and cache.</li>
 213    *
 214    * <li><b>ICacheGroupsProvider</b> - Class implementing the interface <code>ICacheGroupsProvider</code>.
 215    * A developer can implement a method which provides cache groups based on the request,
 216    * the servlect cache administrator and cache.</li>
 217    *
 218    * @param filterConfig The filter configuration
 219    */
 220  0 public void init(FilterConfig filterConfig) {
 221    //Get whatever settings we want...
 222  0 config = filterConfig;
 223  0 admin = ServletCacheAdministrator.getInstance(config.getServletContext());
 224   
 225    // filter parameter time
 226  0 try {
 227  0 time = Integer.parseInt(config.getInitParameter("time"));
 228    } catch (Exception e) {
 229  0 log.info("Could not get init parameter 'time', defaulting to one hour.");
 230    }
 231   
 232    // setting the refresh period for this cache filter
 233  0 expiresRefreshPolicy = new ExpiresRefreshPolicy(time);
 234   
 235    // filter parameter scope
 236  0 try {
 237  0 String scopeString = config.getInitParameter("scope");
 238   
 239  0 if (scopeString.equals("session")) {
 240  0 cacheScope = PageContext.SESSION_SCOPE;
 241  0 } else if (scopeString.equals("application")) {
 242  0 cacheScope = PageContext.APPLICATION_SCOPE;
 243  0 } else if (scopeString.equals("request")) {
 244  0 cacheScope = PageContext.REQUEST_SCOPE;
 245  0 } else if (scopeString.equals("page")) {
 246  0 cacheScope = PageContext.PAGE_SCOPE;
 247    }
 248    } catch (Exception e) {
 249  0 log.info("Could not get init parameter 'scope', defaulting to 'application'.");
 250    }
 251   
 252    // filter parameter fragment
 253  0 try {
 254  0 String fragmentString = config.getInitParameter("fragment");
 255   
 256  0 if (fragmentString.equals("no")) {
 257  0 fragment = FRAGMENT_NO;
 258  0 } else if (fragmentString.equals("yes")) {
 259  0 fragment = FRAGMENT_YES;
 260  0 } else if (fragmentString.equalsIgnoreCase("auto")) {
 261  0 fragment = FRAGMENT_AUTODETECT;
 262    }
 263    } catch (Exception e) {
 264  0 log.info("Could not get init parameter 'fragment', defaulting to 'auto detect'.");
 265    }
 266   
 267    // filter parameter nocache
 268  0 try {
 269  0 String nocacheString = config.getInitParameter("nocache");
 270   
 271  0 if (nocacheString.equals("off")) {
 272  0 nocache = NOCACHE_OFF;
 273  0 } else if (nocacheString.equalsIgnoreCase("sessionIdInURL")) {
 274  0 nocache = NOCACHE_SESSION_ID_IN_URL;
 275    }
 276    } catch (Exception e) {
 277  0 log.info("Could not get init parameter 'nocache', defaulting to 'off'.");
 278    }
 279   
 280    // filter parameter last modified
 281  0 try {
 282  0 String lastModifiedString = config.getInitParameter("lastModified");
 283   
 284  0 if (lastModifiedString.equals("off")) {
 285  0 lastModified = LAST_MODIFIED_OFF;
 286  0 } else if (lastModifiedString.equals("on")) {
 287  0 lastModified = LAST_MODIFIED_ON;
 288  0 } else if (lastModifiedString.equalsIgnoreCase("initial")) {
 289  0 lastModified = LAST_MODIFIED_INITIAL;
 290    }
 291    } catch (Exception e) {
 292  0 log.info("Could not get init parameter 'lastModified', defaulting to 'initial'.");
 293    }
 294   
 295    // filter parameter expires
 296  0 try {
 297  0 String expiresString = config.getInitParameter("expires");
 298   
 299  0 if (expiresString.equals("off")) {
 300  0 expires = EXPIRES_OFF;
 301  0 } else if (expiresString.equals("on")) {
 302  0 expires = EXPIRES_ON;
 303  0 } else if (expiresString.equalsIgnoreCase("time")) {
 304  0 expires = EXPIRES_TIME;
 305    }
 306    } catch (Exception e) {
 307  0 log.info("Could not get init parameter 'expires', defaulting to 'on'.");
 308    }
 309   
 310    // filter parameter ICacheKeyProvider
 311  0 try {
 312  0 String className = config.getInitParameter("ICacheKeyProvider");
 313   
 314  0 try {
 315  0 Class clazz = Class.forName(className);
 316   
 317  0 if (!ICacheKeyProvider.class.isAssignableFrom(clazz)) {
 318  0 log.error("Specified class '" + className + "' does not implement ICacheKeyProvider. Ignoring this provider.");
 319    } else {
 320  0 cacheKeyProvider = (ICacheKeyProvider) clazz.newInstance();
 321    }
 322    } catch (ClassNotFoundException e) {
 323  0 log.error("Class '" + className + "' not found. Ignoring this cache key provider.", e);
 324    } catch (InstantiationException e) {
 325  0 log.error("Class '" + className + "' could not be instantiated because it is not a concrete class. Ignoring this cache key provider.", e);
 326    } catch (IllegalAccessException e) {
 327  0 log.error("Class '" + className + "' could not be instantiated because it is not public. Ignoring this cache key provider.", e);
 328    }
 329    } catch (Exception e) {
 330  0 log.info("Could not get init parameter 'ICacheKeyProvider', defaulting to " + this.getClass().getName() + ".");
 331    }
 332   
 333    // filter parameter ICacheGroupsProvider
 334  0 try {
 335  0 String className = config.getInitParameter("ICacheGroupsProvider");
 336   
 337  0 try {
 338  0 Class clazz = Class.forName(className);
 339   
 340  0 if (!ICacheGroupsProvider.class.isAssignableFrom(clazz)) {
 341  0 log.error("Specified class '" + className + "' does not implement ICacheGroupsProvider. Ignoring this provider.");
 342    } else {
 343  0 cacheGroupsProvider = (ICacheGroupsProvider) clazz.newInstance();
 344    }
 345    } catch (ClassNotFoundException e) {
 346  0 log.error("Class '" + className + "' not found. Ignoring this cache key provider.", e);
 347    } catch (InstantiationException e) {
 348  0 log.error("Class '" + className + "' could not be instantiated because it is not a concrete class. Ignoring this cache groups provider.", e);
 349    } catch (IllegalAccessException e) {
 350  0 log.error("Class '" + className + "' could not be instantiated because it is not public. Ignoring this cache groups provider.", e);
 351    }
 352    } catch (Exception e) {
 353  0 log.info("Could not get init parameter 'ICacheGroupsProvider', defaulting to " + this.getClass().getName() + ".");
 354    }
 355    }
 356   
 357    /**
 358    * @see com.opensymphony.oscache.web.filter.ICacheKeyProvider#createCacheKey(javax.servlet.http.HttpServletRequest, ServletCacheAdministrator, Cache)
 359    */
 360  0 public String createCacheKey(HttpServletRequest httpRequest, ServletCacheAdministrator scAdmin, Cache cache) {
 361  0 return scAdmin.generateEntryKey(null, httpRequest, cacheScope);
 362    }
 363   
 364    /**
 365    * @see com.opensymphony.oscache.web.filter.ICacheGroupsProvider#createCacheGroups(javax.servlet.http.HttpServletRequest, ServletCacheAdministrator, Cache)
 366    */
 367  0 public String[] createCacheGroups(HttpServletRequest httpRequest, ServletCacheAdministrator scAdmin, Cache cache) {
 368  0 return null;
 369    }
 370   
 371    /**
 372    * Checks if the request is a fragment in a page.
 373    *
 374    * According to Java Servlet API 2.2 (8.2.1 Dispatching Requests, Included
 375    * Request Parameters), when a servlet is being used from within an include,
 376    * the attribute <code>javax.servlet.include.request_uri</code> is set.
 377    * According to Java Servlet API 2.3 this is excepted for servlets obtained
 378    * by using the getNamedDispatcher method.
 379    *
 380    * @param request the to be handled request
 381    * @return true if the request is a fragment in a page
 382    */
 383  0 protected boolean isFragment(HttpServletRequest request) {
 384  0 if (fragment == FRAGMENT_AUTODETECT) {
 385  0 return request.getAttribute("javax.servlet.include.request_uri") != null;
 386    } else {
 387  0 return (fragment == FRAGMENT_NO) ? false : true;
 388    }
 389    }
 390   
 391    /**
 392    * Checks if the request was filtered before, so
 393    * guarantees to be executed once per request. You
 394    * can override this methods to define a more specific
 395    * behaviour.
 396    *
 397    * @param request checks if the request was filtered before.
 398    * @return true if it is the first execution
 399    */
 400  0 protected boolean isFilteredBefore(ServletRequest request) {
 401  0 return request.getAttribute(REQUEST_FILTERED) != null;
 402    }
 403   
 404    /**
 405    * isCacheable is a method allowing a subclass to decide if a request is
 406    * cachable or not.
 407    *
 408    * @param request The servlet request
 409    * @return Returns a boolean indicating if the request can be cached or not.
 410    */
 411  0 protected boolean isCacheable(ServletRequest request) {
 412    // TODO implement CACHE-137 and CACHE-141 here
 413  0 boolean cachable = request instanceof HttpServletRequest;
 414   
 415  0 if (cachable) {
 416  0 HttpServletRequest requestHttp = (HttpServletRequest) request;
 417  0 if (nocache == NOCACHE_SESSION_ID_IN_URL) { // don't cache requests if session id is in the URL
 418  0 cachable = !requestHttp.isRequestedSessionIdFromURL();
 419    }
 420    }
 421   
 422  0 if (log.isDebugEnabled()) {
 423  0 log.debug("<cache>: the request " + ((cachable) ? "is" : "is not") + " cachable.");
 424    }
 425   
 426  0 return cachable;
 427    }
 428   
 429    /**
 430    * isCacheable is a method allowing subclass to decide if a response is
 431    * cachable or not.
 432    *
 433    * @param cacheResponse The HTTP servlet response
 434    * @return Returns a boolean indicating if the response can be cached or not.
 435    */
 436  0 protected boolean isCacheable(CacheHttpServletResponseWrapper cacheResponse) {
 437    // TODO implement CACHE-137 and CACHE-141 here
 438    // Only cache if the response was 200
 439  0 boolean cachable = cacheResponse.getStatus() == HttpServletResponse.SC_OK;
 440   
 441  0 if (log.isDebugEnabled()) {
 442  0 log.debug("<cache>: the response " + ((cachable) ? "is" : "is not") + " cachable.");
 443    }
 444   
 445  0 return cachable;
 446    }
 447   
 448    /**
 449    * Check if the client browser support gzip compression.
 450    *
 451    * @param request the http request
 452    * @return true if client browser supports GZIP
 453    */
 454  0 protected boolean acceptsGZipEncoding(HttpServletRequest request) {
 455  0 String acceptEncoding = request.getHeader(HEADER_ACCEPT_ENCODING);
 456  0 return (acceptEncoding != null) && (acceptEncoding.indexOf("gzip") != -1);
 457    }
 458   
 459    }