001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.jcs3.jcache.extras.web; 020 021import javax.cache.Cache; 022import javax.cache.CacheManager; 023import javax.cache.Caching; 024import javax.cache.configuration.FactoryBuilder; 025import javax.cache.configuration.MutableConfiguration; 026import javax.cache.expiry.ExpiryPolicy; 027import javax.cache.integration.CacheLoader; 028import javax.cache.integration.CacheWriter; 029import javax.cache.spi.CachingProvider; 030import javax.servlet.Filter; 031import javax.servlet.FilterChain; 032import javax.servlet.FilterConfig; 033import javax.servlet.ServletException; 034import javax.servlet.ServletRequest; 035import javax.servlet.ServletResponse; 036import javax.servlet.http.Cookie; 037import javax.servlet.http.HttpServletRequest; 038import javax.servlet.http.HttpServletResponse; 039import java.io.BufferedOutputStream; 040import java.io.ByteArrayOutputStream; 041import java.io.IOException; 042import java.io.Serializable; 043import java.net.URI; 044import java.util.Arrays; 045import java.util.Collection; 046import java.util.Enumeration; 047import java.util.List; 048import java.util.Map; 049import java.util.Properties; 050import java.util.zip.GZIPOutputStream; 051 052import static java.util.Collections.list; 053import static javax.servlet.http.HttpServletResponse.SC_OK; 054 055public class JCacheFilter implements Filter 056{ 057 private Cache<PageKey, Page> cache; 058 private CachingProvider provider; 059 private CacheManager manager; 060 061 @Override 062 public void init(final FilterConfig filterConfig) throws ServletException 063 { 064 final ClassLoader classLoader = filterConfig.getServletContext().getClassLoader(); 065 provider = Caching.getCachingProvider(classLoader); 066 067 String uri = filterConfig.getInitParameter("configuration"); 068 if (uri == null) 069 { 070 uri = provider.getDefaultURI().toString(); 071 } 072 final Properties properties = new Properties(); 073 for (final String key : list(filterConfig.getInitParameterNames())) 074 { 075 final String value = filterConfig.getInitParameter(key); 076 if (value != null) 077 { 078 properties.put(key, value); 079 } 080 } 081 manager = provider.getCacheManager(URI.create(uri), classLoader, properties); 082 083 String cacheName = filterConfig.getInitParameter("cache-name"); 084 if (cacheName == null) 085 { 086 cacheName = JCacheFilter.class.getName(); 087 } 088 cache = manager.getCache(cacheName); 089 if (cache == null) 090 { 091 final MutableConfiguration<PageKey, Page> configuration = new MutableConfiguration<PageKey, Page>() 092 .setStoreByValue(false); 093 configuration.setReadThrough("true".equals(properties.getProperty("read-through", "false"))); 094 configuration.setWriteThrough("true".equals(properties.getProperty("write-through", "false"))); 095 if (configuration.isReadThrough()) 096 { 097 configuration.setCacheLoaderFactory(new FactoryBuilder.ClassFactory<CacheLoader<PageKey, Page>>(properties.getProperty("cache-loader-factory"))); 098 } 099 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 static 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}