/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.isis.core.webapp.content; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Locale; import java.util.TimeZone; 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; /** * Adapted from {@link http * ://www.digitalsanctuary.com/tech-blog/java/jboss/setting * -cache-headers-from-jboss.html} * * <p> * Usage: * * <pre> * <filter> * <filter-name>ResourceCachingFilter</filter-name> * <filter-class>org.apache.isis.core.webapp.content.ResourceCachingFilter</filter-class> * <init-param> * <param-name>CacheTime</param-name> * <param-value>86400</param-value> * </init-param> * </filter> * ... * <filter-mapping> * <filter-name>ResourceCachingFilter</filter-name> * <url-pattern>*.css</url-pattern> * </filter-mapping> * <filter-mapping> * <filter-name>ResourceCachingFilter</filter-name> * <url-pattern>*.png</url-pattern> * </filter-mapping> * <filter-mapping> * <filter-name>ResourceCachingFilter</filter-name> * <url-pattern>*.jpg</url-pattern> * </filter-mapping> * <filter-mapping> * <filter-name>ResourceCachingFilter</filter-name> * <url-pattern>*.jpeg</url-pattern> * </filter-mapping> * <filter-mapping> * <filter-name>ResourceCachingFilter</filter-name> * <url-pattern>*.gif</url-pattern> * </filter-mapping> * <filter-mapping> * <filter-name>ResourceCachingFilter</filter-name> * <url-pattern>*.svg</url-pattern> * </filter-mapping> * <filter-mapping> * <filter-name>ResourceCachingFilter</filter-name> * <url-pattern>*.js</url-pattern> * </filter-mapping> * <filter-mapping> * <filter-name>ResourceCachingFilter</filter-name> * <url-pattern>*.html</url-pattern> * </filter-mapping> * <filter-mapping> * <filter-name>ResourceCachingFilter</filter-name> * <url-pattern>*.swf</url-pattern> * </filter-mapping> * </pre> */ public class ResourceCachingFilter implements Filter { /** * Attribute set on {@link HttpServletRequest} if the filter has been * applied. * * <p> * This is intended to inform other filters. */ private static final String REQUEST_ATTRIBUTE = ResourceCachingFilter.class.getName() + ".resource"; /** * To allow other filters to ask whether a request is mapped to the resource * caching filter. * * <p> * For example, the <tt>IsisSessionFilter</tt> uses this in order to skip * any session handling. */ public static boolean isCachedResource(final HttpServletRequest request) { return request.getAttribute(REQUEST_ATTRIBUTE) != null; } /** * The Constant MILLISECONDS_IN_SECOND. */ private static final int MILLISECONDS_IN_SECOND = 1000; /** The Constant POST_CHECK_VALUE. */ private static final String POST_CHECK_VALUE = "post-check="; /** The Constant PRE_CHECK_VALUE. */ private static final String PRE_CHECK_VALUE = "pre-check="; /** The Constant MAX_AGE_VALUE. */ private static final String MAX_AGE_VALUE = "max-age="; /** The Constant ZERO_STRING_VALUE. */ private static final String ZERO_STRING_VALUE = "0"; /** The Constant NO_STORE_VALUE. */ private static final String NO_STORE_VALUE = "no-store"; /** The Constant NO_CACHE_VALUE. */ private static final String NO_CACHE_VALUE = "no-cache"; /** The Constant PRAGMA_HEADER. */ private static final String PRAGMA_HEADER = "Pragma"; /** The Constant CACHE_CONTROL_HEADER. */ private static final String CACHE_CONTROL_HEADER = "Cache-Control"; /** The Constant EXPIRES_HEADER. */ private static final String EXPIRES_HEADER = "Expires"; /** The Constant LAST_MODIFIED_HEADER. */ private static final String LAST_MODIFIED_HEADER = "Last-Modified"; /** The Constant CACHE_TIME_PARAM_NAME. */ private static final String CACHE_TIME_PARAM_NAME = "CacheTime"; /** The default for {@link #CACHE_TIME_PARAM_NAME}. */ private static final String CACHE_TIME_PARAM_NAME_DEFAULT = "" + 86400; /** The reply headers. */ private String[][] mReplyHeaders = { {} }; /** The cache time in seconds. */ private Long cacheTime = 0L; /** * Initializes the Servlet filter with the cache time and sets up the * unchanging headers. * * @param pConfig * the config * * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ @Override public void init(final FilterConfig pConfig) { final ArrayList<String[]> newReplyHeaders = new ArrayList<String[]>(); final String cacheTime = pConfig.getInitParameter(CACHE_TIME_PARAM_NAME); this.cacheTime = Long.parseLong(cacheTime != null ? cacheTime : CACHE_TIME_PARAM_NAME_DEFAULT); if (this.cacheTime > 0L) { newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, MAX_AGE_VALUE + this.cacheTime.longValue() }); newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, PRE_CHECK_VALUE + this.cacheTime.longValue() }); newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, POST_CHECK_VALUE + this.cacheTime.longValue() }); } else { newReplyHeaders.add(new String[] { PRAGMA_HEADER, NO_CACHE_VALUE }); newReplyHeaders.add(new String[] { EXPIRES_HEADER, ZERO_STRING_VALUE }); newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, NO_CACHE_VALUE }); newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, NO_STORE_VALUE }); } this.mReplyHeaders = new String[newReplyHeaders.size()][2]; newReplyHeaders.toArray(this.mReplyHeaders); } /** * Do filter. * * @param servletRequest * the request * @param servletResponse * the response * @param chain * the chain * * @throws IOException * Signals that an I/O exception has occurred. * @throws ServletException * the servlet exception * * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, * javax.servlet.ServletResponse, javax.servlet.FilterChain) */ @Override public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain chain) throws IOException, ServletException { // Apply the headers final HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; final HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; for (final String[] replyHeader : this.mReplyHeaders) { final String name = replyHeader[0]; final String value = replyHeader[1]; httpResponse.addHeader(name, value); } if (this.cacheTime > 0L) { final long now = System.currentTimeMillis(); final DateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); httpDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); httpResponse.addHeader(LAST_MODIFIED_HEADER, httpDateFormat.format(new Date(now))); httpResponse.addHeader(EXPIRES_HEADER, httpDateFormat.format(new Date(now + (this.cacheTime.longValue() * MILLISECONDS_IN_SECOND)))); } httpRequest.setAttribute(REQUEST_ATTRIBUTE, true); chain.doFilter(servletRequest, servletResponse); } /** * Destroy all humans! * * @see javax.servlet.Filter#destroy() */ @Override public void destroy() { } }