001    /*
002     * Copyright 2006 Mat Gessel <mat.gessel@gmail.com>
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package asquare.gwt.tk.server;
017    
018    import java.io.IOException;
019    import java.util.regex.Pattern;
020    
021    import javax.servlet.*;
022    import javax.servlet.http.HttpServletRequest;
023    import javax.servlet.http.HttpServletResponse;
024    
025    /**
026     * This is a filter which enforces proper caching of the generated GWT script
027     * files. It requires that serve your GWT application via a Java servlet
028     * container.
029     * <p>
030     * To use, add the jar to <code>WEB-INF/lib</code> and add the
031     * following to your deployment descriptor (web.xml):
032     * 
033     * <pre>
034     * &lt;filter&gt;
035     *   &lt;filter-name&gt;GWTCacheFilter&lt;/filter-name&gt;
036     *   &lt;filter-class&gt;asquare.gwt.tk.server.GWTCacheFilter&lt;/filter-class&gt;
037     *   &lt;description&gt;Enforces proper caching of GWT script files&lt;/description&gt;
038     * &lt;/filter&gt;
039     * 
040     * &lt;filter-mapping&gt;
041     *   &lt;filter-name&gt;GWTCacheFilter&lt;/filter-name&gt;
042     *   &lt;url-pattern&gt;*.html&lt;/url-pattern&gt;
043     * &lt;/filter-mapping&gt;</pre>
044     * 
045     * By default, files ending in <code>.cache.html</code> are cached and files
046     * ending in <code>.nocache.html</code> are not cached. You can override the
047     * defaults by specifying file name patterns in filter init-params. The pattern
048     * is parsed as a JDK regular expression. The defaults are below: 
049     * 
050     * <pre>
051     * &lt;init-param&gt;
052     *   &lt;param-name&gt;forceDontCache&lt;/param-name&gt;
053     *   &lt;param-value&gt;.+\.nocache.html&lt;/param-value&gt;
054     * &lt;/init-param&gt;
055     * &lt;init-param&gt;
056     *   &lt;param-name&gt;forceCache&lt;/param-name&gt;
057     *   &lt;param-value&gt;.+\.cache.html&lt;/param-value&gt;
058     * &lt;/init-param&gt;</pre>
059     * 
060     * <p>
061     * Usage notes
062     * <ul>
063     * <li>You can verify that the filter is being applied with Firefox's Web
064     * Developer Extension. Click Tools > Web Developer > Information > View
065     * Response Headers.
066     * <li>If you are running an Apache httpd/Jk/Tomcat server configuration you
067     * need to ensure that Tomcat is serving HTML files, otherwise the filter will
068     * not be applied.
069     * <li>One reason that this filter exists is that you cannot use <code>*.nocache.html</code> or
070     * <code>*.cache.html</code> for url patterns. According to the 2.3 servlet
071     * spec, an extension is defined as the characters after the <strong>last</strong>
072     * period.
073     * <li>The header is modified <em>before</em> passing control down the filter chain. 
074     * </ul>
075     * 
076     * @see <a
077     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">Cache-control
078     *      directive</a>
079     * @see <a
080     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21">Expires
081     *      directive</a>
082     * @see <a
083     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.32">Pragma
084     *      directive</a>
085     */
086    public class GWTCacheFilter implements Filter
087    {
088            /**
089             * The name of the filter init-param which specifies files not to cache. 
090             * The name is <code>{@value}</code>. 
091             */
092            public static final String INITPARAM_FORCEDONTCACHE = "forceDontCache";
093            
094            /**
095             * The name of the filter init-param which specifies files to cache. 
096             * The name is <code>{@value}</code>. 
097             */
098            public static final String INITPARAM_FORCECACHE = "forceCache";
099            
100            /**
101             * The default value of the <code>forceCache</code> init-param. 
102             * The value is <code>{@value}</code>. 
103             */
104            public static final String DEFAULT_FORCEDONTCACHE = ".+\\.nocache.html";
105            
106            /**
107             * The default value of the <code>forceDontCache</code> init-param. 
108             * The value is <code>{@value}</code>. 
109             */
110            public static final String DEFAULT_FORCECACHE = ".+\\.cache.html";
111            
112            private Pattern forceDontCachePattern;
113            private Pattern forceCachePattern;
114            
115            /*
116             * (non-Javadoc)
117             * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
118             */
119            public void init(FilterConfig filterConfig) throws ServletException
120            {
121                    String forceDontCachePatternString = filterConfig.getInitParameter(INITPARAM_FORCEDONTCACHE);
122                    String forceCachePatternString = filterConfig.getInitParameter(INITPARAM_FORCECACHE);
123                    if (forceDontCachePatternString == null)
124                    {
125                            forceDontCachePatternString = DEFAULT_FORCEDONTCACHE;
126                    }
127                    if (forceCachePatternString == null)
128                    {
129                            forceCachePatternString = DEFAULT_FORCECACHE;
130                    }
131                    forceDontCachePattern = Pattern.compile(forceDontCachePatternString);
132                    forceCachePattern = Pattern.compile(forceCachePatternString);
133            }
134            
135            /*
136             * (non-Javadoc)
137             * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
138             */
139            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
140            {
141                    if (request instanceof HttpServletRequest)
142                    {
143                            HttpServletRequest hRequest = (HttpServletRequest) request;
144                            if (forceDontCachePattern.matcher(hRequest.getRequestURL()).matches())
145                            {
146                                    HttpServletResponse hResponse = (HttpServletResponse) response;
147                                    hResponse.addHeader("Cache-Control", "no-cache no-store must-revalidate");
148                                    hResponse.addHeader("Pragma", "no-cache"); // HTTP/1.0
149                                    hResponse.setDateHeader("Expires", 0l);
150                            }
151                            else if (forceCachePattern.matcher(hRequest.getRequestURL()).matches())
152                            {
153                                    HttpServletResponse hresponse = (HttpServletResponse) response;
154                                    
155                                    // the w3c spec requires a maximum age of 1 year
156                                    hresponse.addHeader("Cache-Control", "max-age=31536000");
157                                    hresponse.setDateHeader("Expires", System.currentTimeMillis() + 31536000000l);
158                            }
159                    }
160                    chain.doFilter(request, response);
161            }
162            
163            /*
164             * (non-Javadoc)
165             * @see javax.servlet.Filter#destroy()
166             */
167            public void destroy()
168            {
169            }
170    }