package org.sigmah.server.servlet.filter; /* * #%L * Sigmah * %% * Copyright (C) 2010 - 2016 URD * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import java.io.IOException; import java.util.Date; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import com.google.inject.Singleton; /** * <p> * Instructs browsers to cache application ressources "until the sun explodes" (or actually a year). Exceptions are made * for files containing the token ".nocache." * </p> * <p> * See http://www.infoq.com/articles/gwt-high-ajax * See http://www.gwtproject.org/doc/latest/DevGuideCompilingAndDebugging.html#perfect_caching * </p> * <p> * Apache configuration example: * * <pre> * <Files *.nocache.*> * ExpiresActive on * ExpiresDefault "now" * Header merge Cache-Control "public, max-age=0, must-revalidate" * </Files> * * <Files *.cache.*> * ExpiresActive on * ExpiresDefault "now plus 1 year" * Header set dumbHeader "dumb header flag to control filter mechanism." * </Files> * </pre> * * </p> * * @author Denis Colliot (dcolliot@ideia.fr) */ @Singleton public class CacheFilter implements Filter { /** * Resources containing one of these value(s) will <b>never</b> be cached. * * @see #containsAny(String, String...) */ private static final String[] NO_CACHE_FILTERS = { ".nocache.", "manifest" }; /** * Resources containing one of these value(s) will be cached. * * @see #containsAny(String, String...) */ private static final String[] CACHE_FILTERS = { ".cache.", "/gxt" }; /** * Year period in milliseconds. */ private static final long YEAR = 365 * 24 * 60 * 60 * 1000L; /** * Sigmah custom header. * Used to control that responses headers are correctly set. */ private static final String CUSTOM_HEADER_NAME = "sigmahHeader"; /** * {@inheritDoc} */ @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain) throws IOException, ServletException { if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { // Just in case. filterChain.doFilter(request, response); return; } final HttpServletRequest httpRequest = (HttpServletRequest) request; final HttpServletResponse httpResponse = (HttpServletResponse) response; final String requestURI = httpRequest.getRequestURI(); if (containsAny(requestURI, CACHE_FILTERS)) { // Cached resource. httpResponse.setDateHeader("Expires", new Date().getTime() + YEAR); // Control flag. httpResponse.setHeader(CUSTOM_HEADER_NAME, "Resource cached by Sigmah filter."); } else if (containsAny(requestURI, NO_CACHE_FILTERS)) { // Never cached resource. httpResponse.setDateHeader("Expires", 0); httpResponse.setHeader("Cache-Control", "no-cache, public, max-age=0, must-revalidate"); // Set IE extended HTTP/1.1 no-cache headers (use addHeader). httpResponse.addHeader("Cache-Control", "post-check=0, pre-check=0"); // Set standard HTTP/1.0 no-cache header. httpResponse.setHeader("Pragma", "no-cache"); // Control flag. httpResponse.setHeader(CUSTOM_HEADER_NAME, "Resource NOT cached by Sigmah filter."); } // Define content-type for JSON files. if(requestURI.contains(".json")) { httpResponse.setContentType("application/json"); } filterChain.doFilter(request, response); } /** * {@inheritDoc} */ @Override public void init(final FilterConfig filterConfig) throws ServletException { // Nothing to initialize. } /** * {@inheritDoc} */ @Override public void destroy() { // Nothing to destroy. } /** * Returns if the given {@code seq} contains any of the given {@code searchSeqs}. * The search is case insensitive. * * <pre> * containsAny(null, null) → false * containsAny(*, null) → false * containsAny(null, *) → false * containsAny("abc", "a") → true * containsAny("abc", "abcd", "efg") → false * containsAny("abc", "aB") → true * containsAny("abc", "aBk") → false * containsAny("abc", "aBk", "abC") → true * </pre> * * @param seq * The sequence. * @param searchSeqs * The search sequence(s). * @return {@code true} if the given {@code seq} contains any of the given {@code searchSeqs}, {@code false} * otherwise. */ private static boolean containsAny(final String seq, final String... searchSeqs) { if (seq == null || searchSeqs == null) { return false; } for (final String searchSeq : searchSeqs) { if (StringUtils.containsIgnoreCase(seq, searchSeq)) { return true; } } return false; } }