History | Log In     View a printable version of the current page.  
Issue Details (XML | Word | Printable)

Key: CACHE-141
Type: Task Task
Status: Closed Closed
Resolution: Fixed
Priority: Major Major
Assignee: Lars Torunski
Reporter: Laurent Dauvilaire
Votes: 3
Watchers: 4
Operations

If you were logged in you would be able to see more operations.
OSCache

CacheFilter easier sub-classing

Created: 18/Feb/05 05:14 AM   Updated: 19/Mar/07 05:57 PM
Component/s: Filters
Affects Version/s: 2.1
Fix Version/s: 2.4

Sub-Tasks  All   Open   
 Sub-Task Progress: 

 Description  « Hide
I think that would be nice to add some improvements to the CacheFilter class in order to make it easier to have a sub-class.

For example a "isCachable(request, response)" method could be called before to store a response into the cache, giving the possibility to implemented a custom algorithm in a sub-class.

A fine implementation of the method would be to test those "Pragme: no-cache" or "Cache-Control: private" response headers ...

Another good point would be to have a method called to decide whether a cache entry sould be used or not.

Suggestion:

/**
     * isCachable is a method allowing subclass to decide if a response
     * id cachable or not.
     *
     * @param request The HTTP servlet request
     * @param response The HTTP servlet response
     * @return Returns a boolean indicating if the request can be cached or not.
     */
    protected boolean isCachable(HttpServletRequest request,
                                 HttpServletResponse response) {

Boolean cachable = true ;
        
if (response.containsHeader("Cache-control")) {
            cachable = false ;
        } else {
            cachable = true ;
        }

if (log.isDebugEnabled()) {
            log.debug("<cache>: the response "
                      + ((cachable.booleanValue()) ? "is" : "is not")
                      + " cachable.");
        }
        return cachable ;
    }

/**
     * doFilterPreprocess is a method allowing subclass to perform actions
     * at the start of the filtering process.
     *
     * @param request The HTTP servlet request
     * @param response The HTTP servlet response
     * @param key The cache key used to access the cache
     * @param cache The cache object
     */
    protected void doFilterPreprocess(HttpServletRequest request,
                                      HttpServletResponse response,
                                      String key,
                                      Cache cache) {
        
if (log.isDebugEnabled()) {
            log.debug("<cache>: doFilter for key " + key + " is starting.");
        }
    }

/**
     * doFilterPostprocess is a method allowing subclass to perform actions
     * at the end of the filtering process.
     *
     * @param request The HTTP servlet request
     * @param response The HTTP servlet response
     * @param key The cache key used to access the cache
     * @param cache The cache object
     */
    protected void doFilterPostprocess(HttpServletRequest request,
                                       HttpServletResponse response,
                                       String key,
                                       Cache cache) {
        if (log.isDebugEnabled()) {
            log.debug("<cache>: doFilter for key " + key + " has been done.");
        }
    }

/**
     * useCache is a method allowing subclass to decide if the use
     * of the cache is requested or not.
     *
     * @param request The HTTP servlet request
     * @param response The HTTP servlet response
     * @param key The cache key used to access the cache
     * @param cache The cache object
     * @return Returns a boolean indicating if the caching is requested or not.
     */
    protected boolean useCache(HttpServletRequest request,
                               HttpServletResponse response,
                               String key,
                               Cache cache) {
        boolean useIt = true ;

if (log.isDebugEnabled()) {
            log.debug("<cache>: the cache " + ((useIt) ? "will" : "will not") + " be used.");
        }
        return useIt ;
    }
    

    /**
     * The doFilter call caches the response by wrapping the <code>HttpServletResponse</code>
     * object so that the output stream can be caught.
     * This works by splitting off the output stream
     * into two with the {@link SplitServletOutputStream} class.
     * One stream gets written out to the response as normal,
     * the other is fed into a byte array inside a {@link ResponseContent}
     * object.
     *
     * @param request The servlet request
     * @param response The servlet response
     * @param chain The filter chain
     * @throws ServletException IOException
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (log.isInfoEnabled()) {
            log.info("<cache>: filter in scope " + cacheScope);
        }

HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String key = admin.generateEntryKey(null, httpRequest, cacheScope);
        Cache cache = admin.getCache(httpRequest, cacheScope);
        boolean cachedResponse ;
        
doFilterPreprocess(httpRequest, httpResponse, key, cache) ;
        boolean useCache = useCache(httpRequest, httpResponse, key, cache) ;

if (useCache) {
            try {
                if (log.isInfoEnabled()) {
                    log.info("<cache>: Reading Caching entry " + key + " ...");
                }
                ResponseContent respContent = (ResponseContent) cache.getFromCache(key, time);

long clientLastModified = httpRequest.getDateHeader("If-Modified-Since"); // will return -1 if no header...
                if (clientLastModified >= respContent.getLastModified()) {
                    httpResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                } else {
                    respContent.writeTo(response);
                }
                cachedResponse = true ;
            } catch (NeedsRefreshException nre) {
                cachedResponse = false ;
            }
        } else {
            cachedResponse = false ;
        }

if (! cachedResponse) {
            boolean updateSucceeded = false;

try {
                if (log.isInfoEnabled()) {
                    log.info("<cache>: New cache entry, cache stale or cache scope flushed for " + key);
                }

CacheHttpServletResponseWrapper cacheResponse = new CacheHttpServletResponseWrapper(httpResponse);
                chain.doFilter(request, cacheResponse);
                cacheResponse.flushBuffer();

// Only cache if the response was 200
                if (cacheResponse.getStatus() == HttpServletResponse.SC_OK) {
                    if (isCachable(httpRequest, httpResponse)) {
                        //Store as the cache content the result of the response
                        cache.putInCache(key, cacheResponse.getContent());
                        updateSucceeded = true;
                        }
                    }
            } finally {
                if (!updateSucceeded) {
                    cache.cancelUpdate(key);
                }
            }
        }

doFilterPreprocess(httpRequest, httpResponse, key, cache) ;
    }





Regards
Laurent

 All   Comments   Change History      Sort Order:
Lars Torunski - [09/Apr/05 01:04 PM ]
We can't support/implement all methods at once. But we will create sub tasks to document the changes.

Lars Torunski - [25/Feb/07 04:51 PM ]
changing most protected method signatures to public visibility

Lars Torunski - [26/Feb/07 01:48 PM ]
one fix, two won't fixes and one issue was a duplicate -> closing this task