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
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
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
92 throw e;
93 } finally {
94 Thread.currentThread().setName(originalThreadName);
95 }
96 return pageInfo;
97 }
98
99
100
101
102
103
104
105
106
107
108
109 protected PageInfo buildPage(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, long timeToLiveSeconds) throws AlreadyGzippedException, Exception {
110
111 final ByteArrayOutputStream outstr = new ByteArrayOutputStream();
112 final GenericResponseWrapper wrapper = new GenericResponseWrapper(response, outstr);
113 chain.doFilter(request, wrapper);
114 wrapper.flush();
115
116
117 return new PageInfo(wrapper.getStatus(), wrapper.getContentType(), wrapper.getHeaders(), wrapper.getCookies(),
118 outstr.toByteArray(), true, timeToLiveSeconds);
119 }
120
121
122
123
124
125
126
127
128
129
130
131
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
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
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
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
241
242
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 }