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}