/subprojects/groovy-servlet/src/main/java/groovy/servlet/TemplateServlet.java
Java | 506 lines | 238 code | 46 blank | 222 comment | 57 complexity | 6af9e1ae721ac65d009623113ad624f4 MD5 | raw file
- /**
- * 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 groovy.servlet;
- import groovy.text.SimpleTemplateEngine;
- import groovy.text.Template;
- import groovy.text.TemplateEngine;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.Reader;
- import java.io.Writer;
- import java.net.URL;
- import java.util.Date;
- import java.util.Map;
- import java.util.WeakHashMap;
- import javax.servlet.ServletConfig;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- /**
- * A generic servlet for serving (mostly HTML) templates.
- * <p>
- * It delegates work to a <code>groovy.text.TemplateEngine</code> implementation
- * processing HTTP requests.
- * <p>
- * <h4>Usage</h4>
- * <p>
- * <code>helloworld.html</code> is a headless HTML-like template
- * <pre><code>
- * <html>
- * <body>
- * <% 3.times { %>
- * Hello World!
- * <% } %>
- * <br>
- * </body>
- * </html>
- * </code></pre>
- * <p>
- * Minimal <code>web.xml</code> example serving HTML-like templates
- * <pre><code>
- * <web-app>
- * <servlet>
- * <servlet-name>template</servlet-name>
- * <servlet-class>groovy.servlet.TemplateServlet</servlet-class>
- * </servlet>
- * <servlet-mapping>
- * <servlet-name>template</servlet-name>
- * <url-pattern>*.html</url-pattern>
- * </servlet-mapping>
- * </web-app>
- * </code></pre>
- * <p>
- * <h4>Template engine configuration</h4>
- * <p>
- * By default, the TemplateServer uses the {@link groovy.text.SimpleTemplateEngine}
- * which interprets JSP-like templates. The init parameter <code>template.engine</code>
- * defines the fully qualified class name of the template to use:
- * <pre>
- * template.engine = [empty] - equals groovy.text.SimpleTemplateEngine
- * template.engine = groovy.text.SimpleTemplateEngine
- * template.engine = groovy.text.GStringTemplateEngine
- * template.engine = groovy.text.XmlTemplateEngine
- * </pre>
- * <p>
- * <h3>Servlet Init Parameters</h3>
- * <p>
- * <h4>Logging and extra-output options</h4>
- * <p>
- * This implementation provides a verbosity flag switching log statements.
- * The servlet init parameter name is:
- * <pre>
- * generated.by = true(default) | false
- * </pre>
- * <p>
- * <h4>Groovy Source Encoding Parameter</h4>
- * <p>
- * The following servlet init parameter name can be used to specify the encoding TemplateServlet will use
- * to read the template groovy source files:
- * <pre>
- * groovy.source.encoding
- * </pre>
- *
- * @author Christian Stein
- * @author Guillaume Laforge
- * @version 2.0
- * @see TemplateServlet#setVariables(ServletBinding)
- */
- public class TemplateServlet extends AbstractHttpServlet {
- /**
- * Simple cache entry. If a file is supplied, then the entry is validated against
- * last modified and length attributes of the specified file.
- *
- * @author Christian Stein
- */
- private static class TemplateCacheEntry {
- Date date;
- long hit;
- long lastModified;
- long length;
- Template template;
- public TemplateCacheEntry(File file, Template template) {
- this(file, template, false); // don't get time millis for sake of speed
- }
- public TemplateCacheEntry(File file, Template template, boolean timestamp) {
- if (template == null) {
- throw new NullPointerException("template");
- }
- if (timestamp) {
- this.date = new Date(System.currentTimeMillis());
- } else {
- this.date = null;
- }
- this.hit = 0;
- if (file != null) {
- this.lastModified = file.lastModified();
- this.length = file.length();
- }
- this.template = template;
- }
- /**
- * Checks the passed file attributes against those cached ones.
- *
- * @param file Other file handle to compare to the cached values. May be null in which case the validation is skipped.
- * @return <code>true</code> if all measured values match, else <code>false</code>
- */
- public boolean validate(File file) {
- if (file != null) {
- if (file.lastModified() != this.lastModified) {
- return false;
- }
- if (file.length() != this.length) {
- return false;
- }
- }
- hit++;
- return true;
- }
- public String toString() {
- if (date == null) {
- return "Hit #" + hit;
- }
- return "Hit #" + hit + " since " + date;
- }
- }
- /**
- * Simple file name to template cache map.
- */
- private final Map<String, TemplateCacheEntry> cache;
- /**
- * Underlying template engine used to evaluate template source files.
- */
- private TemplateEngine engine;
- /**
- * Flag that controls the appending of the "Generated by ..." comment.
- */
- private boolean generateBy;
- private String fileEncodingParamVal;
- private static final String GROOVY_SOURCE_ENCODING = "groovy.source.encoding";
- /**
- * Create new TemplateServlet.
- */
- public TemplateServlet() {
- this.cache = new WeakHashMap<String, TemplateCacheEntry>();
- this.engine = null; // assigned later by init()
- this.generateBy = true; // may be changed by init()
- this.fileEncodingParamVal = null; // may be changed by init()
- }
- /**
- * Find a cached template for a given key. If a <code>File</code> is passed then
- * any cached object is validated against the File to determine if it is out of
- * date
- * @param key a unique key for the template, such as a file's absolutePath or a URL.
- * @param file a file to be used to determine if the cached template is stale. May be null.
- * @return The cached template, or null if there was no cached entry, or the entry was stale.
- */
- private Template findCachedTemplate(String key, File file) {
- Template template = null;
- /*
- * Test cache for a valid template bound to the key.
- */
- if (verbose) {
- log("Looking for cached template by key \"" + key + "\"");
- }
-
- TemplateCacheEntry entry = (TemplateCacheEntry) cache.get(key);
- if (entry != null) {
- if (entry.validate(file)) {
- if (verbose) {
- log("Cache hit! " + entry);
- }
- template = entry.template;
- } else {
- if (verbose) {
- log("Cached template " + key + " needs recompiliation! " + entry);
- }
- }
- } else {
- if (verbose) {
- log("Cache miss for " + key);
- }
- }
- return template;
- }
- /**
- * Compile the template and store it in the cache.
- * @param key a unique key for the template, such as a file's absolutePath or a URL.
- * @param reader a reader for the template's source.
- * @param file a file to be used to determine if the cached template is stale. May be null.
- * @return the created template.
- * @throws Exception Any exception when creating the template.
- */
- private Template createAndStoreTemplate(String key, InputStream inputStream, File file) throws Exception {
- if (verbose) {
- log("Creating new template from " + key + "...");
- }
- Reader reader = null;
- try {
- String fileEncoding = (fileEncodingParamVal != null) ? fileEncodingParamVal :
- System.getProperty(GROOVY_SOURCE_ENCODING);
- reader = fileEncoding == null ? new InputStreamReader(inputStream) : new InputStreamReader(inputStream, fileEncoding);
- Template template = engine.createTemplate(reader);
- cache.put(key, new TemplateCacheEntry(file, template, verbose));
- if (verbose) {
- log("Created and added template to cache. [key=" + key + "] " + cache.get(key));
- }
- //
- // Last sanity check.
- //
- if (template == null) {
- throw new ServletException("Template is null? Should not happen here!");
- }
- return template;
- } finally {
- if (reader != null) {
- reader.close();
- } else if (inputStream != null) {
- inputStream.close();
- }
- }
- }
- /**
- * Gets the template created by the underlying engine parsing the request.
- *
- * <p>
- * This method looks up a simple (weak) hash map for an existing template
- * object that matches the source file. If the source file didn't change in
- * length and its last modified stamp hasn't changed compared to a precompiled
- * template object, this template is used. Otherwise, there is no or an
- * invalid template object cache entry, a new one is created by the underlying
- * template engine. This new instance is put to the cache for consecutive
- * calls.
- *
- * @return The template that will produce the response text.
- * @param file The file containing the template source.
- * @throws ServletException If the request specified an invalid template source file
- */
- protected Template getTemplate(File file) throws ServletException {
- String key = file.getAbsolutePath();
- Template template = findCachedTemplate(key, file);
- //
- // Template not cached or the source file changed - compile new template!
- //
- if (template == null) {
- try {
- template = createAndStoreTemplate(key, new FileInputStream(file), file);
- } catch (Exception e) {
- throw new ServletException("Creation of template failed: " + e, e);
- }
- }
- return template;
- }
- /**
- * Gets the template created by the underlying engine parsing the request.
- *
- * <p>
- * This method looks up a simple (weak) hash map for an existing template
- * object that matches the source URL. If there is no cache entry, a new one is
- * created by the underlying template engine. This new instance is put
- * to the cache for consecutive calls.
- *
- * @return The template that will produce the response text.
- * @param url The URL containing the template source..
- * @throws ServletException If the request specified an invalid template source URL
- */
- protected Template getTemplate(URL url) throws ServletException {
- String key = url.toString();
- Template template = findCachedTemplate(key, null);
- // Template not cached or the source file changed - compile new template!
- if (template == null) {
- try {
- template = createAndStoreTemplate(key, url.openConnection().getInputStream(), null);
- } catch (Exception e) {
- throw new ServletException("Creation of template failed: " + e, e);
- }
- }
- return template;
- }
- /**
- * Initializes the servlet from hints the container passes.
- * <p>
- * Delegates to sub-init methods and parses the following parameters:
- * <ul>
- * <li> <tt>"generatedBy"</tt> : boolean, appends "Generated by ..." to the
- * HTML response text generated by this servlet.
- * </li>
- * </ul>
- *
- * @param config Passed by the servlet container.
- * @throws ServletException if this method encountered difficulties
- * @see TemplateServlet#initTemplateEngine(ServletConfig)
- */
- public void init(ServletConfig config) throws ServletException {
- super.init(config);
- this.engine = initTemplateEngine(config);
- if (engine == null) {
- throw new ServletException("Template engine not instantiated.");
- }
- String value = config.getInitParameter("generated.by");
- if (value != null) {
- this.generateBy = Boolean.valueOf(value);
- }
- value = config.getInitParameter(GROOVY_SOURCE_ENCODING);
- if (value != null) {
- this.fileEncodingParamVal = value;
- }
- log("Servlet " + getClass().getName() + " initialized on " + engine.getClass());
- }
- /**
- * Creates the template engine.
- * <p>
- * Called by {@link TemplateServlet#init(ServletConfig)} and returns just
- * <code>new groovy.text.SimpleTemplateEngine()</code> if the init parameter
- * <code>template.engine</code> is not set by the container configuration.
- *
- * @param config Current servlet configuration passed by the container.
- * @return The underlying template engine or <code>null</code> on error.
- */
- protected TemplateEngine initTemplateEngine(ServletConfig config) {
- String name = config.getInitParameter("template.engine");
- if (name == null) {
- return new SimpleTemplateEngine();
- }
- try {
- return (TemplateEngine) Class.forName(name).newInstance();
- } catch (InstantiationException e) {
- log("Could not instantiate template engine: " + name, e);
- } catch (IllegalAccessException e) {
- log("Could not access template engine class: " + name, e);
- } catch (ClassNotFoundException e) {
- log("Could not find template engine class: " + name, e);
- }
- return null;
- }
- /**
- * Services the request with a response.
- * <p>
- * First the request is parsed for the source file uri. If the specified file
- * could not be found or can not be read an error message is sent as response.
- *
- * @param request The http request.
- * @param response The http response.
- * @throws IOException if an input or output error occurs while the servlet is handling the HTTP request
- * @throws ServletException if the HTTP request cannot be handled
- */
- public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- if (verbose) {
- log("Creating/getting cached template...");
- }
- //
- // Get the template source file handle.
- //
- Template template;
- long getMillis;
- String name;
-
- File file = super.getScriptUriAsFile(request);
- if (file != null) {
- name = file.getName();
- if (!file.exists()) {
- response.sendError(HttpServletResponse.SC_NOT_FOUND);
- return; // throw new IOException(file.getAbsolutePath());
- }
- if (!file.canRead()) {
- response.sendError(HttpServletResponse.SC_FORBIDDEN, "Can not read \"" + name + "\"!");
- return; // throw new IOException(file.getAbsolutePath());
- }
- getMillis = System.currentTimeMillis();
- template = getTemplate(file);
- getMillis = System.currentTimeMillis() - getMillis;
- } else {
- name = super.getScriptUri(request);
- URL url = servletContext.getResource(name);
- getMillis = System.currentTimeMillis();
- template = getTemplate(url);
- getMillis = System.currentTimeMillis() - getMillis;
- }
- //
- // Create new binding for the current request.
- //
- ServletBinding binding = new ServletBinding(request, response, servletContext);
- setVariables(binding);
- //
- // Prepare the response buffer content type _before_ getting the writer.
- // and set status code to ok
- //
- response.setContentType(CONTENT_TYPE_TEXT_HTML + "; charset=" + encoding);
- response.setStatus(HttpServletResponse.SC_OK);
- //
- // Get the output stream writer from the binding.
- //
- Writer out = (Writer) binding.getVariable("out");
- if (out == null) {
- out = response.getWriter();
- }
- //
- // Evaluate the template.
- //
- if (verbose) {
- log("Making template \"" + name + "\"...");
- }
- // String made = template.make(binding.getVariables()).toString();
- // log(" = " + made);
- long makeMillis = System.currentTimeMillis();
- template.make(binding.getVariables()).writeTo(out);
- makeMillis = System.currentTimeMillis() - makeMillis;
- if (generateBy) {
- StringBuilder sb = new StringBuilder(100);
- sb.append("\n<!-- Generated by Groovy TemplateServlet [create/get=");
- sb.append(Long.toString(getMillis));
- sb.append(" ms, make=");
- sb.append(Long.toString(makeMillis));
- sb.append(" ms] -->\n");
- out.write(sb.toString());
- }
- //
- // flush the response buffer.
- //
- response.flushBuffer();
- if (verbose) {
- log("Template \"" + name + "\" request responded. [create/get=" + getMillis + " ms, make=" + makeMillis + " ms]");
- }
- }
- }