View Javadoc

1   package org.cateproject.view.cache;
2   
3   import java.io.BufferedOutputStream;
4   import java.io.ByteArrayOutputStream;
5   import java.io.IOException;
6   import java.io.OutputStream;
7   import java.util.Collection;
8   import java.util.Enumeration;
9   import java.util.HashMap;
10  import java.util.Iterator;
11  import java.util.Map;
12  import java.util.zip.DataFormatException;
13  
14  import javax.servlet.FilterChain;
15  import javax.servlet.http.Cookie;
16  import javax.servlet.http.HttpServletRequest;
17  import javax.servlet.http.HttpServletResponse;
18  
19  import org.slf4j.Logger;
20  import org.slf4j.LoggerFactory;
21  
22  import net.sf.ehcache.Ehcache;
23  import net.sf.ehcache.Element;
24  import net.sf.ehcache.constructs.blocking.LockTimeoutException;
25  import net.sf.ehcache.constructs.web.AlreadyCommittedException;
26  import net.sf.ehcache.constructs.web.AlreadyGzippedException;
27  import net.sf.ehcache.constructs.web.GenericResponseWrapper;
28  import net.sf.ehcache.constructs.web.PageInfo;
29  import net.sf.ehcache.constructs.web.ResponseHeadersNotModifiableException;
30  import net.sf.ehcache.constructs.web.ResponseUtil;
31  import net.sf.ehcache.constructs.web.SerializableCookie;
32  
33  public abstract class CateCachingFilter {
34  	private static final Logger logger = LoggerFactory.getLogger(CateCachingFilter.class);
35  	
36  	public abstract boolean filter(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws Exception;
37  	
38  	protected abstract String calculateKey(HttpServletRequest httpRequest) throws Exception;
39  	
40  	protected void doFilterInternal(final String key, final Ehcache cache, final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws Exception {
41  		PageInfo pageInfo = buildPageInfo(key,cache,request, response, chain);
42  
43          if (pageInfo.isOk()) {
44              if (response.isCommitted()) {
45                  throw new AlreadyCommittedException("Response already committed after doing buildPage"
46                          + " but before writing response from PageInfo.");
47              }
48              writeResponse(request, response, pageInfo);
49          }
50  	}
51  	
52  	protected PageInfo buildPageInfo(final String key, final Ehcache cache, final HttpServletRequest request, final HttpServletResponse response,
53  			final FilterChain chain) throws Exception {
54  
55  		PageInfo pageInfo = null;
56  		String originalThreadName = Thread.currentThread().getName();
57  		try {
58  			long start = System.currentTimeMillis();
59  			logger.debug("Looking for " + key);
60  			Element element = cache.get(key);
61  			
62  			if (element == null || element.getObjectValue() == null) {
63  				try {
64  					// Page is not cached - build the response, cache it, and send to client
65  					pageInfo = buildPage(request, response, chain, cache.getCacheConfiguration().getTimeToLiveSeconds());
66  					if (pageInfo.isOk()) {
67  						if (logger.isDebugEnabled()) {
68  							logger.debug("PageInfo ok. Adding to cache " + cache.getName() + " with key " + key);
69  						}
70  						cache.put(new Element(key, pageInfo));
71  						
72  					} else {
73  						if (logger.isDebugEnabled()) {
74  							logger.debug("PageInfo was not ok(200). Putting null into cache " + cache.getName() + " with key " + key);
75  						}
76  						cache.put(new Element(key, pageInfo));
77  					}					
78  				} catch (final Throwable throwable) {
79  					// Must unlock the cache if the above fails. Will be logged at Filter
80  					cache.put(new Element(key, pageInfo));
81  					throw new Exception(throwable);
82  				}
83  				if (logger.isInfoEnabled()) {
84  			        logger.info("Cache MISS! [" + (System.currentTimeMillis() - start) + " msecs]");
85  				}
86  			} else {
87  				pageInfo = (PageInfo)element.getObjectValue();
88  				logger.info("Cache HIT! [" + (System.currentTimeMillis() - start) + " msecs]");
89  			}
90  		} catch (LockTimeoutException e) {
91  			//do not release the lock, because you never acquired it
92  			throw e;
93  		} finally {
94  			Thread.currentThread().setName(originalThreadName);
95  		}
96  		return pageInfo;
97  	}
98  	
99  	/**
100      * Builds the PageInfo object by passing the request along the filter chain
101      *
102      * @param request
103      * @param response
104      * @param chain
105      * @return a Serializable value object for the page or page fragment
106      * @throws AlreadyGzippedException if an attempt is made to double gzip the body
107      * @throws Exception
108      */
109     protected PageInfo buildPage(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, long timeToLiveSeconds) throws AlreadyGzippedException, Exception {
110         // Invoke the next entity in the chain
111         final ByteArrayOutputStream outstr = new ByteArrayOutputStream();
112         final GenericResponseWrapper wrapper = new GenericResponseWrapper(response, outstr);
113         chain.doFilter(request, wrapper);
114         wrapper.flush();
115 
116         // Return the page info
117         return new PageInfo(wrapper.getStatus(), wrapper.getContentType(), wrapper.getHeaders(), wrapper.getCookies(),
118                 outstr.toByteArray(), true, timeToLiveSeconds);
119     }
120 
121     /**
122      * Writes the response from a PageInfo object.
123      * <p/>
124      * Headers are set last so that there is an opportunity to override 
125      *
126      * @param request
127      * @param response
128      * @param pageInfo
129      * @throws IOException
130      * @throws DataFormatException
131      * @throws ResponseHeadersNotModifiableException
132      *
133      */
134     protected void writeResponse(final HttpServletRequest request, final HttpServletResponse response, final PageInfo pageInfo)
135             throws IOException, DataFormatException, ResponseHeadersNotModifiableException {
136         boolean requestAcceptsGzipEncoding = acceptsGzipEncoding(request);
137 
138         setStatus(response, pageInfo);
139         setContentType(response, pageInfo);
140         setCookies(pageInfo, response);
141         //do headers last so that users can override with their own header sets
142         setHeaders(pageInfo, requestAcceptsGzipEncoding, response);
143         writeContent(request, response, pageInfo);
144     }
145     
146     protected void setCookies(final PageInfo pageInfo, final HttpServletResponse response) {
147 
148         final Collection cookies = pageInfo.getSerializableCookies();
149         for (Iterator iterator = cookies.iterator(); iterator.hasNext();) {
150             final Cookie cookie = ((SerializableCookie) iterator.next()).toCookie();
151             response.addCookie(cookie);
152         }
153     }
154     
155     protected void setHeaders(final PageInfo pageInfo,
156     		boolean requestAcceptsGzipEncoding,
157     		final HttpServletResponse response) {
158 
159     	final Collection headers = pageInfo.getResponseHeaders();
160     	final int header = 0;
161     	final int value = 1;
162 
163     	for (Iterator iterator = headers.iterator(); iterator.hasNext();) {
164     		final String[] headerPair = (String[]) iterator.next();
165     		response.addHeader(headerPair[header], headerPair[value]);
166     	}
167     }
168 
169 
170     protected void setContentType(final HttpServletResponse response, final PageInfo pageInfo) {
171         String contentType = pageInfo.getContentType();
172         if (contentType != null && contentType.length() >  0) {
173             response.setContentType(contentType);
174         }
175     }
176     
177     protected void setStatus(final HttpServletResponse response, final PageInfo pageInfo) {
178         response.setStatus(pageInfo.getStatusCode());
179     }
180     
181     protected boolean acceptsGzipEncoding(HttpServletRequest request) {
182         return acceptsEncoding(request, "gzip");
183     }
184     
185     /**
186      * Checks if request accepts the named encoding.
187      */
188     protected boolean acceptsEncoding(final HttpServletRequest request, final String name) {
189         final boolean accepts = headerContains(request, "Accept-Encoding", name);
190         return accepts;
191     }
192     
193     protected void writeContent(final HttpServletRequest request,
194     		final HttpServletResponse response, final PageInfo pageInfo)
195     throws IOException, ResponseHeadersNotModifiableException {
196     	byte[] body;
197 
198     	boolean shouldBodyBeZero = ResponseUtil.shouldBodyBeZero(request, pageInfo.getStatusCode());
199     	if (shouldBodyBeZero) {
200     		body = new byte[0];
201     	} else if (acceptsGzipEncoding(request)) {
202     		body = pageInfo.getGzippedBody();
203     		if (ResponseUtil.shouldGzippedBodyBeZero(body, request)) {
204     			body = new byte[0];
205     		} else {
206     			ResponseUtil.addGzipHeader(response);
207     		}
208 
209     	} else {
210     		body = pageInfo.getUngzippedBody();
211     	}
212 
213 
214     	response.setContentLength(body.length);
215     	OutputStream out = new BufferedOutputStream(response.getOutputStream());
216     	out.write(body);
217     	out.flush();
218     }
219 
220 
221     
222     /**
223      * Checks if request contains the header value.
224      */
225     private boolean headerContains(final HttpServletRequest request, final String header, final String value) {
226 
227         logRequestHeaders(request);
228 
229         final Enumeration accepted = request.getHeaders(header);
230         while (accepted.hasMoreElements()) {
231             final String headerValue = (String) accepted.nextElement();
232             if (headerValue.indexOf(value) != -1) {
233                 return true;
234             }
235         }
236         return false;
237     }
238     
239     /**
240      * Logs the request headers, if debug is enabled.
241      *
242      * @param request
243      */
244     protected void logRequestHeaders(final HttpServletRequest request) {
245         if (logger.isTraceEnabled()) {
246             Map headers = new HashMap();
247             Enumeration enumeration = request.getHeaderNames();
248             StringBuffer logLine = new StringBuffer();
249             logLine.append("Request Headers");
250             while (enumeration.hasMoreElements()) {
251                 String name = (String) enumeration.nextElement();
252                 String headerValue = request.getHeader(name);
253                 headers.put(name, headerValue);
254                 logLine.append(": ").append(name).append(" -> ").append(headerValue);
255             }
256             logger.trace(logLine.toString());
257         }
258     }
259 
260 }