/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/kernel/trunk/api/src/main/java/org/sakaiproject/util/ResponseHeaderFilter.java $ * $Id: $ *********************************************************************************** * * Copyright (c) 2010 Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.util; import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; 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.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.component.api.ServerConfigurationService; /** * ResponseHeaderFilter is responsible for adding response headers to the HttpServletResponses. * e.g. It can be used to cache resources like *.css and *.js * * To use the filter: * Add the filter definition to the web.xml and specify the response header information * in the init parameters and add the appropriate filter mapping. * <code> * <filter> * <description>Response Header Filter to enable JS caching</description> * <display-name>Cache Filter For One Week</display-name> * <filter-name>CacheFilterForWeek</filter-name> * <filter-class>org.sakaiproject.util.ResponseHeaderFilter</filter-class> * <init-param> * <param-name>Cache-Control</param-name> * <param-value>max-age=604800, public</param-value> * </init-param> * </filter> * <filter-mapping> * <filter-name>CacheFilterForWeek</filter-name> * <url-pattern>/js/*</url-pattern> * </filter-mapping> * </code> */ public class ResponseHeaderFilter implements Filter { private static Log log = LogFactory.getLog(ResponseHeaderFilter.class); private Map<String,String> headerMap = new ConcurrentHashMap<String, String>(); public Map<String, String> getHeaderMap() { return new HashMap<String, String>(this.headerMap); } /** * Adds a header or replaces an existing one or clears a header if the value is null * * @param name the header name * @param value the header value * @return the previous value associated with the name OR null if none * @throws IllegalArgumentException if the name is null or empty */ public String addHeader(String name, String value) { if (name == null || "".equals(name)) { throw new IllegalArgumentException("header name cannot be null or blank"); } if (this.headerMap != null) { if (value == null) { // remove the header value return this.headerMap.remove(name); } else { // add / update the header value return this.headerMap.put(name, value); } } return null; } @SuppressWarnings("unchecked") public void init(FilterConfig filterConfig) throws ServletException { String webappName = filterConfig.getServletContext().getServletContextName(); // storing the header information in a local map for (Enumeration<String> paramNames = filterConfig.getInitParameterNames(); paramNames.hasMoreElements(); ) { String paramName = paramNames.nextElement(); String paramValue = filterConfig.getInitParameter(paramName); if (paramName != null && paramValue != null) { this.headerMap.put(paramName, paramValue); } } // adding the configured ones from sakai config ServerConfigurationService serverConfigurationService = org.sakaiproject.component.cover.ServerConfigurationService.getInstance(); if (serverConfigurationService != null) { String[] headerStrings = serverConfigurationService.getStrings("response.headers"); if (headerStrings != null) { for (String headerString : headerStrings) { if (headerString != null && ! "".equals(headerString)) { int loc = headerString.indexOf("::"); if (loc <= 0) { log.warn("Invalid header string in sakai config (must contain '::', e.g. key::value): " + headerString); continue; } String name = headerString.substring(0, loc); if (name == null || "".equals(name)) { log.warn("Invalid header string in sakai config (name must not be empty): " + headerString); continue; } String value = null; if (headerString.length() > loc+2) { value = headerString.substring(loc+2); } addHeader(name, value); if (value == null) { log.info("Removing header ("+name+") from all responses for current webapp: " + webappName); } else { log.info("Adding header ("+name+" -> "+value+") to all responses for current webapp: " + webappName); } } } } } log.info("INIT: for webapp " + webappName); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // If the response is a http servlet response add the header information if (response instanceof HttpServletResponse) { HttpServletResponse httpResponse = (HttpServletResponse) response; for (Entry<String, String> headerEntry: this.headerMap.entrySet()) { httpResponse.addHeader(headerEntry.getKey(), headerEntry.getValue()); } } chain.doFilter(request, response); } public void destroy() { if (this.headerMap != null) { this.headerMap.clear(); } log.info("DESTROY"); } }