001: /*
002: * Copyright 2003 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of 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,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package velosurf.util;
018:
019: import javax.servlet.Filter;
020: import javax.servlet.FilterConfig;
021: import javax.servlet.ServletException;
022: import javax.servlet.ServletRequest;
023: import javax.servlet.ServletResponse;
024: import javax.servlet.FilterChain;
025: import javax.servlet.ServletContext;
026: import javax.servlet.RequestDispatcher;
027: import javax.servlet.http.HttpServletRequest;
028: import javax.servlet.http.HttpServletResponse;
029:
030: import java.io.IOException;
031: import java.io.PrintWriter;
032: import java.util.Enumeration;
033: import java.util.LinkedList;
034: import java.util.Set;
035: import java.util.Iterator;
036: import java.util.HashSet;
037:
038: /**
039: * <p>This class is a forwarding filter which allows to hide ".vtl" from resource URLs.
040: * It works by building a cache of all template names in the webapp, and adding </p>
041: *
042: * <p>The purpose of this feature is to allow URLs to be independant of the status of the resource:
043: * regular file or template file, allowing this status to transparently change over time.
044: * You can store all resources in the same directory tree, templates having
045: * an additional ".vtl" like in "foo.html.vtl" or "bar.js.vtl".</p>
046: *
047: * <p>In development mode, you can choose either to reset the cache periodically,
048: * or manually with the "reset-cache" URI, or both.</p>
049: *
050: * <p>Initialization parameters:
051: * <ul>
052: * <li>template-extension: the targeted template extension (default: ".vtl").</li>
053: * <li>reset-method: "periodic" or "manual" or "both" or "none" (default: "none").<li>
054: * <li>reset-uri: the rest uri, for manual resets (default: "/reset-cache").
055: * <li>reset-period: the period, in seconds, between two resets, for periodic resets (default: 120s).</li>
056: * </ul>
057: * </p>
058: *
059: * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
060: *
061: */
062:
063: public class TemplateNameFilter implements Filter {
064:
065: /** the servlet context. */
066: private ServletContext servletContext;
067:
068: /** targeted template extension. */
069: private String templateExtension = ".vtl";
070:
071: /** NONE reset method. */
072: private static final int RESET_NONE = 0;
073: /** MANUAL reset method. */
074: private static final int RESET_MANUAL = 1;
075: /** PERIODIC reset method. */
076: private static final int RESET_PERIODIC = 2;
077: /** reset method. */
078: private int resetMethod = RESET_NONE; /* bit-masked */
079:
080: /** reset uri. */
081: private String resetUri = "/reset-cache";
082:
083: /** reset period. */
084: private long resetPeriod = 120000; /* millisec */
085:
086: /* the set of template names. */
087: private Set<String> templates = null;
088:
089: /* the time of the last reset. */
090: private long lastReset;
091:
092: /**
093: * init the filter.
094: * @param filterConfig filter configuration
095: * @throws ServletException
096: */
097: public void init(FilterConfig filterConfig) throws ServletException {
098: servletContext = filterConfig.getServletContext();
099:
100: if (!Logger.isInitialized() && servletContext != null) {
101: Logger.setWriter(new ServletLogWriter(servletContext));
102: }
103:
104: /* init parameters */
105: String param, value;
106: Enumeration params = filterConfig.getInitParameterNames();
107: while (params.hasMoreElements()) {
108: param = (String) params.nextElement();
109: value = filterConfig.getInitParameter(param);
110: if ("template-extension".equals(param)) {
111: if (!value.startsWith(".")) {
112: value = "." + value;
113: }
114: templateExtension = value;
115: } else if ("reset-method".equals(param)) {
116: if ("manual".equals(value)) {
117: resetMethod = RESET_MANUAL;
118: } else if ("periodic".equals(value)) {
119: resetMethod = RESET_PERIODIC;
120: } else if ("both".equals(value)) {
121: resetMethod = RESET_MANUAL | RESET_PERIODIC;
122: } else if (!"none".equals(value)) {
123: servletContext
124: .log("[warn] TemplateNameFilter: reset-method should be one of 'none', 'manual', 'pediodic' or 'both'.");
125: }
126: } else if ("request-uri".equals(param)) {
127: resetUri = value;
128: } else if ("reset-period".equals(param)) {
129: try {
130: resetPeriod = Integer.parseInt(value) * 1000;
131: } catch (NumberFormatException nfe) {
132: servletContext
133: .log("[warn] TemplateNameFilter: reset-period should be a number!");
134: }
135: } else {
136: servletContext
137: .log("[warn] TemplateNameFilter: unknown parameter '"
138: + param + "' ignored.");
139: }
140: }
141:
142: /* builds the cache */
143: buildsTemplateNamesList(null);
144: }
145:
146: /**
147: * Build the cache, which consists of a hash set containing all template names.
148: *
149: */
150: private synchronized void buildsTemplateNamesList(
151: HttpServletResponse response) {
152: /* check again if the reset is necessary, the current thread may have been
153: waiting to enter this method during the last reset */
154: if ((resetMethod & RESET_PERIODIC) != 0
155: && System.currentTimeMillis() - lastReset < resetPeriod
156: && templates != null) {
157: return;
158: }
159:
160: Set<String> result = new HashSet<String>();
161:
162: String path, entry;
163: Set entries;
164: LinkedList<String> paths = new LinkedList<String>();
165: paths.add("/");
166: while (paths.size() > 0) {
167: path = (String) paths.removeFirst();
168: entries = servletContext.getResourcePaths(path);
169: for (Iterator i = entries.iterator(); i.hasNext();) {
170: entry = (String) i.next();
171: /* ignore some entries... */
172: if (entry.endsWith("/WEB-INF/")
173: || entry.endsWith("/.svn/")) {
174: continue;
175: }
176: if (entry.endsWith("/")) {
177: paths.add(entry);
178: } else if (entry.endsWith(templateExtension)) {
179: result.add(entry.substring(0, entry.length()
180: - templateExtension.length()));
181: }
182: }
183: }
184: templates = result;
185: lastReset = System.currentTimeMillis();
186:
187: if (response != null) {
188: try {
189: PrintWriter writer = response.getWriter();
190: writer
191: .println("<html><body>Cache reseted.</body></html>");
192: } catch (IOException ioe) {
193:
194: }
195: }
196: }
197:
198: /**
199: * doFilter method.
200: * @param servletRequest request
201: * @param servletResponse response
202: * @param filterChain filter chain
203: * @throws ServletException
204: * @throws IOException
205: */
206: public void doFilter(ServletRequest servletRequest,
207: ServletResponse servletResponse, FilterChain filterChain)
208: throws ServletException, IOException {
209:
210: HttpServletRequest request = (HttpServletRequest) servletRequest;
211: HttpServletResponse response = (HttpServletResponse) servletResponse;
212:
213: String path = request.getRequestURI();
214: String query = request.getQueryString();
215: if (query == null) {
216: query = "";
217: } else {
218: query = "?" + query;
219: }
220: Logger.trace("--------------------------------");
221: Logger.trace("URI = " + path + query + " (Referer: "
222: + request.getHeader("Referer") + ")");
223: /* I've been said some buggy containers where leaving the query string in the uri */
224: int i;
225: if ((i = path.indexOf("?")) != -1) {
226: path = path.substring(0, i);
227: }
228:
229: /* if the extension is already present, let it go... */
230: if (path.endsWith(templateExtension)) {
231: filterChain.doFilter(servletRequest, servletResponse);
232: return;
233: }
234:
235: /* is it time for a reset ? */
236: long now = System.currentTimeMillis();
237: if ((resetMethod & RESET_MANUAL) != 0 && path.equals(resetUri)) {
238: lastReset = now - 2 * resetPeriod;
239: buildsTemplateNamesList(response);
240: } else if ((resetMethod & RESET_PERIODIC) != 0
241: && now - lastReset > resetPeriod) {
242: buildsTemplateNamesList(response);
243: } else {
244: if (templates.contains(path)) {
245: /* forward the request with extension added */
246: Logger.trace("vtl: forwarding request towards " + path
247: + templateExtension + query);
248: RequestDispatcher dispatcher = servletContext
249: .getRequestDispatcher(path + templateExtension
250: + query);
251: dispatcher.forward(request, servletResponse);
252: } else {
253: /* normal processing */
254: filterChain.doFilter(servletRequest, servletResponse);
255: }
256: }
257: }
258:
259: /** Destroy the filter.
260: *
261: */
262: public void destroy() {
263: }
264: }
|