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 */ 019 020package org.apache.isis.core.webapp.content; 021 022import java.io.IOException; 023import java.text.DateFormat; 024import java.text.SimpleDateFormat; 025import java.util.ArrayList; 026import java.util.Date; 027import java.util.Locale; 028import java.util.TimeZone; 029 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.HttpServletRequest; 037import javax.servlet.http.HttpServletResponse; 038 039/** 040 * Adapted from {@link http 041 * ://www.digitalsanctuary.com/tech-blog/java/jboss/setting 042 * -cache-headers-from-jboss.html} 043 * 044 * <p> 045 * Usage: 046 * 047 * <pre> 048 * <filter> 049 * <filter-name>ResourceCachingFilter</filter-name> 050 * <filter-class>org.apache.isis.core.webapp.content.ResourceCachingFilter</filter-class> 051 * <init-param> 052 * <param-name>CacheTime</param-name> 053 * <param-value>86400</param-value> 054 * </init-param> 055 * </filter> 056 * ... 057 * <filter-mapping> 058 * <filter-name>ResourceCachingFilter</filter-name> 059 * <url-pattern>*.js</url-pattern> 060 * </filter-mapping> 061 * <filter-mapping> 062 * <filter-name>ResourceCachingFilter</filter-name> 063 * <url-pattern>*.css</url-pattern> 064 * </filter-mapping> 065 * <filter-mapping> 066 * <filter-name>ResourceCachingFilter</filter-name> 067 * <url-pattern>*.jpg</url-pattern> 068 * </filter-mapping> 069 * <filter-mapping> 070 * <filter-name>ResourceCachingFilter</filter-name> 071 * <url-pattern>*.png</url-pattern> 072 * </filter-mapping> 073 * <filter-mapping> 074 * <filter-name>ResourceCachingFilter</filter-name> 075 * <url-pattern>*.gif</url-pattern> 076 * </filter-mapping> 077 * </pre> 078 */ 079public class ResourceCachingFilter implements Filter { 080 081 /** 082 * Attribute set on {@link HttpServletRequest} if the filter has been 083 * applied. 084 * 085 * <p> 086 * This is intended to inform other filters. 087 */ 088 private static final String REQUEST_ATTRIBUTE = ResourceCachingFilter.class.getName() + ".resource"; 089 090 /** 091 * To allow other filters to ask whether a request is mapped to the resource 092 * caching filter. 093 * 094 * <p> 095 * For example, the <tt>IsisSessionFilter</tt> uses this in order to skip 096 * any session handling. 097 */ 098 public static boolean isCachedResource(final HttpServletRequest request) { 099 return request.getAttribute(REQUEST_ATTRIBUTE) != null; 100 } 101 102 /** 103 * The Constant MILLISECONDS_IN_SECOND. 104 */ 105 private static final int MILLISECONDS_IN_SECOND = 1000; 106 107 /** The Constant POST_CHECK_VALUE. */ 108 private static final String POST_CHECK_VALUE = "post-check="; 109 110 /** The Constant PRE_CHECK_VALUE. */ 111 private static final String PRE_CHECK_VALUE = "pre-check="; 112 113 /** The Constant MAX_AGE_VALUE. */ 114 private static final String MAX_AGE_VALUE = "max-age="; 115 116 /** The Constant ZERO_STRING_VALUE. */ 117 private static final String ZERO_STRING_VALUE = "0"; 118 119 /** The Constant NO_STORE_VALUE. */ 120 private static final String NO_STORE_VALUE = "no-store"; 121 122 /** The Constant NO_CACHE_VALUE. */ 123 private static final String NO_CACHE_VALUE = "no-cache"; 124 125 /** The Constant PRAGMA_HEADER. */ 126 private static final String PRAGMA_HEADER = "Pragma"; 127 128 /** The Constant CACHE_CONTROL_HEADER. */ 129 private static final String CACHE_CONTROL_HEADER = "Cache-Control"; 130 131 /** The Constant EXPIRES_HEADER. */ 132 private static final String EXPIRES_HEADER = "Expires"; 133 134 /** The Constant LAST_MODIFIED_HEADER. */ 135 private static final String LAST_MODIFIED_HEADER = "Last-Modified"; 136 137 /** The Constant CACHE_TIME_PARAM_NAME. */ 138 private static final String CACHE_TIME_PARAM_NAME = "CacheTime"; 139 140 /** The default for {@link #CACHE_TIME_PARAM_NAME}. */ 141 private static final String CACHE_TIME_PARAM_NAME_DEFAULT = "" + 86400; 142 143 /** The reply headers. */ 144 private String[][] mReplyHeaders = { {} }; 145 146 /** The cache time in seconds. */ 147 private Long cacheTime = 0L; 148 149 /** 150 * Initializes the Servlet filter with the cache time and sets up the 151 * unchanging headers. 152 * 153 * @param pConfig 154 * the config 155 * 156 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) 157 */ 158 @Override 159 public void init(final FilterConfig pConfig) { 160 final ArrayList<String[]> newReplyHeaders = new ArrayList<String[]>(); 161 final String cacheTime = pConfig.getInitParameter(CACHE_TIME_PARAM_NAME); 162 this.cacheTime = Long.parseLong(cacheTime != null ? cacheTime : CACHE_TIME_PARAM_NAME_DEFAULT); 163 if (this.cacheTime > 0L) { 164 newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, MAX_AGE_VALUE + this.cacheTime.longValue() }); 165 newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, PRE_CHECK_VALUE + this.cacheTime.longValue() }); 166 newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, POST_CHECK_VALUE + this.cacheTime.longValue() }); 167 } else { 168 newReplyHeaders.add(new String[] { PRAGMA_HEADER, NO_CACHE_VALUE }); 169 newReplyHeaders.add(new String[] { EXPIRES_HEADER, ZERO_STRING_VALUE }); 170 newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, NO_CACHE_VALUE }); 171 newReplyHeaders.add(new String[] { CACHE_CONTROL_HEADER, NO_STORE_VALUE }); 172 } 173 this.mReplyHeaders = new String[newReplyHeaders.size()][2]; 174 newReplyHeaders.toArray(this.mReplyHeaders); 175 } 176 177 /** 178 * Do filter. 179 * 180 * @param servletRequest 181 * the request 182 * @param servletResponse 183 * the response 184 * @param chain 185 * the chain 186 * 187 * @throws IOException 188 * Signals that an I/O exception has occurred. 189 * @throws ServletException 190 * the servlet exception 191 * 192 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, 193 * javax.servlet.ServletResponse, javax.servlet.FilterChain) 194 */ 195 @Override 196 public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain chain) throws IOException, ServletException { 197 // Apply the headers 198 final HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; 199 final HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; 200 for (final String[] replyHeader : this.mReplyHeaders) { 201 final String name = replyHeader[0]; 202 final String value = replyHeader[1]; 203 httpResponse.addHeader(name, value); 204 } 205 if (this.cacheTime > 0L) { 206 final long now = System.currentTimeMillis(); 207 final DateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); 208 httpDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); 209 httpResponse.addHeader(LAST_MODIFIED_HEADER, httpDateFormat.format(new Date(now))); 210 httpResponse.addHeader(EXPIRES_HEADER, httpDateFormat.format(new Date(now + (this.cacheTime.longValue() * MILLISECONDS_IN_SECOND)))); 211 } 212 httpRequest.setAttribute(REQUEST_ATTRIBUTE, true); 213 chain.doFilter(servletRequest, servletResponse); 214 } 215 216 /** 217 * Destroy all humans! 218 * 219 * @see javax.servlet.Filter#destroy() 220 */ 221 @Override 222 public void destroy() { 223 } 224 225}