View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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 }