/*
* 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() {
}
}