1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.jcs.jcache.extras.web;
20
21 import javax.cache.Cache;
22 import javax.cache.CacheManager;
23 import javax.cache.Caching;
24 import javax.cache.configuration.FactoryBuilder;
25 import javax.cache.configuration.MutableConfiguration;
26 import javax.cache.expiry.ExpiryPolicy;
27 import javax.cache.integration.CacheLoader;
28 import javax.cache.integration.CacheWriter;
29 import javax.cache.spi.CachingProvider;
30 import javax.servlet.Filter;
31 import javax.servlet.FilterChain;
32 import javax.servlet.FilterConfig;
33 import javax.servlet.ServletException;
34 import javax.servlet.ServletRequest;
35 import javax.servlet.ServletResponse;
36 import javax.servlet.http.Cookie;
37 import javax.servlet.http.HttpServletRequest;
38 import javax.servlet.http.HttpServletResponse;
39 import java.io.BufferedOutputStream;
40 import java.io.ByteArrayOutputStream;
41 import java.io.IOException;
42 import java.io.Serializable;
43 import java.net.URI;
44 import java.util.Arrays;
45 import java.util.Collection;
46 import java.util.Enumeration;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Properties;
50 import java.util.zip.GZIPOutputStream;
51
52 import static java.util.Collections.list;
53 import static javax.servlet.http.HttpServletResponse.SC_OK;
54
55 public class JCacheFilter implements Filter
56 {
57 private Cache<PageKey, Page> cache;
58 private CachingProvider provider;
59 private CacheManager manager;
60
61 @Override
62 public void init(final FilterConfig filterConfig) throws ServletException
63 {
64 final ClassLoader classLoader = filterConfig.getServletContext().getClassLoader();
65 provider = Caching.getCachingProvider(classLoader);
66
67 String uri = filterConfig.getInitParameter("configuration");
68 if (uri == null)
69 {
70 uri = provider.getDefaultURI().toString();
71 }
72 final Properties properties = new Properties();
73 for (final String key : list(filterConfig.getInitParameterNames()))
74 {
75 final String value = filterConfig.getInitParameter(key);
76 if (value != null)
77 {
78 properties.put(key, value);
79 }
80 }
81 manager = provider.getCacheManager(URI.create(uri), classLoader, properties);
82
83 String cacheName = filterConfig.getInitParameter("cache-name");
84 if (cacheName == null)
85 {
86 cacheName = JCacheFilter.class.getName();
87 }
88 cache = manager.getCache(cacheName);
89 if (cache == null)
90 {
91 final MutableConfiguration<PageKey, Page> configuration = new MutableConfiguration<PageKey, Page>()
92 .setStoreByValue(false);
93 configuration.setReadThrough("true".equals(properties.getProperty("read-through", "false")));
94 configuration.setWriteThrough("true".equals(properties.getProperty("write-through", "false")));
95 if (configuration.isReadThrough())
96 {
97 configuration.setCacheLoaderFactory(new FactoryBuilder.ClassFactory<CacheLoader<PageKey, Page>>(properties.getProperty("cache-loader-factory")));
98 }
99 if (configuration.isWriteThrough())
100 {
101 configuration.setCacheWriterFactory(new FactoryBuilder.ClassFactory<CacheWriter<? super PageKey, ? super Page>>(properties.getProperty("cache-writer-factory")));
102 }
103 final String expirtyPolicy = properties.getProperty("expiry-policy-factory");
104 if (expirtyPolicy != null)
105 {
106 configuration.setExpiryPolicyFactory(new FactoryBuilder.ClassFactory<ExpiryPolicy>(expirtyPolicy));
107 }
108 configuration.setManagementEnabled("true".equals(properties.getProperty("management-enabled", "false")));
109 configuration.setStatisticsEnabled("true".equals(properties.getProperty("statistics-enabled", "false")));
110 cache = manager.createCache(cacheName, configuration);
111 }
112 }
113
114 @Override
115 public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException
116 {
117 boolean gzip = false;
118 if (HttpServletRequest.class.isInstance(servletRequest))
119 {
120 final Enumeration<String> acceptEncoding = HttpServletRequest.class.cast(servletRequest).getHeaders("Accept-Encoding");
121 while (acceptEncoding != null && acceptEncoding.hasMoreElements())
122 {
123 if ("gzip".equals(acceptEncoding.nextElement()))
124 {
125 gzip = true;
126 break;
127 }
128 }
129 }
130
131 final HttpServletResponse httpServletResponse = HttpServletResponse.class.cast(servletResponse);
132 checkResponse(httpServletResponse);
133
134 final PageKey key = new PageKey(key(servletRequest), gzip);
135 Page page = cache.get(key);
136 if (page == null)
137 {
138 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
139 final InMemoryResponse response;
140 if (gzip)
141 {
142 response = new InMemoryResponse(httpServletResponse, new GZIPOutputStream(baos));
143 }
144 else
145 {
146 response = new InMemoryResponse(httpServletResponse, baos);
147 }
148 filterChain.doFilter(servletRequest, response);
149 response.flushBuffer();
150
151 page = new Page(
152 response.getStatus(),
153 response.getContentType(),
154 response.getContentLength(),
155 response.getCookies(),
156 response.getHeaders(),
157 baos.toByteArray());
158 cache.put(key, page);
159 }
160
161 if (page.status == SC_OK) {
162 checkResponse(httpServletResponse);
163
164 if (gzip)
165 {
166 httpServletResponse.setHeader("Content-Encoding", "gzip");
167 }
168
169 httpServletResponse.setStatus(page.status);
170 if (page.contentType != null)
171 {
172 httpServletResponse.setContentType(page.contentType);
173 }
174 if (page.contentLength > 0)
175 {
176 httpServletResponse.setContentLength(page.contentLength);
177 }
178 for (final Cookie c : page.cookies)
179 {
180 httpServletResponse.addCookie(c);
181 }
182 for (final Map.Entry<String, List<Serializable>> entry : page.headers.entrySet())
183 {
184 for (final Serializable value : entry.getValue())
185 {
186 if (Integer.class.isInstance(value))
187 {
188 httpServletResponse.addIntHeader(entry.getKey(), Integer.class.cast(value));
189 }
190 else if (String.class.isInstance(value))
191 {
192 httpServletResponse.addHeader(entry.getKey(), String.class.cast(value));
193 }
194 else if (Long.class.isInstance(value))
195 {
196 httpServletResponse.addDateHeader(entry.getKey(), Long.class.cast(value));
197 }
198 }
199 }
200 httpServletResponse.setContentLength(page.out.length);
201 final BufferedOutputStream bos = new BufferedOutputStream(httpServletResponse.getOutputStream());
202 if (page.out.length != 0)
203 {
204 bos.write(page.out);
205 }
206 else
207 {
208 bos.write(new byte[0]);
209 }
210 bos.flush();
211 }
212 }
213
214 protected String key(final ServletRequest servletRequest)
215 {
216 if (HttpServletRequest.class.isInstance(servletRequest))
217 {
218 final HttpServletRequest request = HttpServletRequest.class.cast(servletRequest);
219 return request.getMethod() + '_' + request.getRequestURI() + '_' + request.getQueryString();
220 }
221 return servletRequest.toString();
222 }
223
224 private void checkResponse(final ServletResponse servletResponse)
225 {
226 if (servletResponse.isCommitted()) {
227 throw new IllegalStateException("Response committed");
228 }
229 }
230
231 @Override
232 public void destroy()
233 {
234 if (!cache.isClosed())
235 {
236 cache.close();
237 }
238 if (!manager.isClosed())
239 {
240 manager.close();
241 }
242 provider.close();
243 }
244
245 protected static class PageKey implements Serializable {
246 private final String uri;
247 private boolean gzip;
248
249 public PageKey(final String uri, final boolean gzip)
250 {
251 this.uri = uri;
252 this.gzip = gzip;
253 }
254
255 public void setGzip(final boolean gzip)
256 {
257 this.gzip = gzip;
258 }
259
260 @Override
261 public boolean equals(final Object o)
262 {
263 if (this == o)
264 {
265 return true;
266 }
267 if (o == null || getClass() != o.getClass())
268 {
269 return false;
270 }
271
272 final PageKey pageKey = PageKey.class.cast(o);
273 return gzip == pageKey.gzip && uri.equals(pageKey.uri);
274
275 }
276
277 @Override
278 public int hashCode()
279 {
280 int result = uri.hashCode();
281 result = 31 * result + (gzip ? 1 : 0);
282 return result;
283 }
284 }
285
286 protected static class Page implements Serializable {
287 private final int status;
288 private final String contentType;
289 private final int contentLength;
290 private final Collection<Cookie> cookies;
291 private final Map<String, List<Serializable>> headers;
292 private final byte[] out;
293
294 public Page(final int status,
295 final String contentType, final int contentLength,
296 final Collection<Cookie> cookies, final Map<String, List<Serializable>> headers,
297 final byte[] out)
298 {
299 this.status = status;
300 this.contentType = contentType;
301 this.contentLength = contentLength;
302 this.cookies = cookies;
303 this.headers = headers;
304 this.out = out;
305 }
306
307 @Override
308 public boolean equals(final Object o)
309 {
310 if (this == o)
311 {
312 return true;
313 }
314 if (o == null || getClass() != o.getClass())
315 {
316 return false;
317 }
318
319 final Page page = Page.class.cast(o);
320 return contentLength == page.contentLength
321 && status == page.status
322 && !(contentType != null ? !contentType.equals(page.contentType) : page.contentType != null)
323 && cookies.equals(page.cookies)
324 && headers.equals(page.headers)
325 && Arrays.equals(out, page.out);
326
327 }
328
329 @Override
330 public int hashCode()
331 {
332 int result = status;
333 result = 31 * result + (contentType != null ? contentType.hashCode() : 0);
334 result = 31 * result + contentLength;
335 result = 31 * result + cookies.hashCode();
336 result = 31 * result + headers.hashCode();
337 result = 31 * result + Arrays.hashCode(out);
338 return result;
339 }
340 }
341 }