All Downloads are FREE. Search and download functionalities are using the official Maven repository.

groovy.servlet.TemplateServlet Maven / Gradle / Ivy

There is a newer version: 1.5.8
Show newest version
/*
 * Copyright 2003-2007 the original author or authors.
 *
 * Licensed 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.FileReader;
import java.io.IOException;
import java.io.Writer;
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.
 * 
 * 

* It delegates work to a groovy.text.TemplateEngine implementation * processing HTTP requests. * *

Usage

* * helloworld.html is a headless HTML-like template *

 *  <html>
 *    <body>
 *      <% 3.times { %>
 *        Hello World!
 *      <% } %>
 *      <br>
 *    </body>
 *  </html> 
 * 
* * Minimal web.xml example serving HTML-like templates *

 * <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>
 * 
* *

Template engine configuration

* *

* By default, the TemplateServer uses the {@link groovy.text.SimpleTemplateEngine} * which interprets JSP-like templates. The init parameter template.engine * defines the fully qualified class name of the template to use: *

 *   template.engine = [empty] - equals groovy.text.SimpleTemplateEngine
 *   template.engine = groovy.text.SimpleTemplateEngine
 *   template.engine = groovy.text.GStringTemplateEngine
 *   template.engine = groovy.text.XmlTemplateEngine
 * 
* *

Logging and extra-output options

* *

* This implementation provides a verbosity flag switching log statements. * The servlet init parameter name is: *

 *   generate.by = true(default) | false
 * 
* * @see TemplateServlet#setVariables(ServletBinding) * * @author Christian Stein * @author Guillaume Laforge * @version 2.0 */ public class TemplateServlet extends AbstractHttpServlet { /** * Simple cache entry that validates 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 (file == null) { throw new NullPointerException("file"); } if (template == null) { throw new NullPointerException("template"); } if (timestamp) { this.date = new Date(System.currentTimeMillis()); } else { this.date = null; } this.hit = 0; 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. * @return true if all measured values match, else false */ public boolean validate(File file) { if (file == null) { throw new NullPointerException("file"); } 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 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; /** * Create new TemplateSerlvet. */ public TemplateServlet() { this.cache = new WeakHashMap(); this.engine = null; // assigned later by init() this.generateBy = true; // may be changed by init() } /** * Gets the template created by the underlying engine parsing the request. * *

* 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 HttpServletRequest. * @throws ServletException * If the request specified an invalid template source file */ protected Template getTemplate(File file) throws ServletException { String key = file.getAbsolutePath(); 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 needs recompiliation!"); } } } else { if (verbose) { log("Cache miss."); } } // // Template not cached or the source file changed - compile new template! // if (template == null) { if (verbose) { log("Creating new template from file " + file + "..."); } FileReader reader = null; try { reader = new FileReader(file); template = engine.createTemplate(reader); } catch (Exception e) { throw new ServletException("Creation of template failed: " + e, e); } finally { if (reader != null) { try { reader.close(); } catch (IOException ignore) { // e.printStackTrace(); } } } cache.put(key, new TemplateCacheEntry(file, template, verbose)); if (verbose) { log("Created and added template to cache. [key=" + key + "]"); } } // // Last sanity check. // if (template == null) { throw new ServletException("Template is null? Should not happen here!"); } return template; } /** * Initializes the servlet from hints the container passes. *

* Delegates to sub-init methods and parses the following parameters: *

    *
  • "generatedBy" : boolean, appends "Generated by ..." to the * HTML response text generated by this servlet. *
  • *
* @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).booleanValue(); } log("Servlet " + getClass().getName() + " initialized on " + engine.getClass()); } /** * Creates the template engine. * * Called by {@link TemplateServlet#init(ServletConfig)} and returns just * new groovy.text.SimpleTemplateEngine() if the init parameter * template.engine is not set by the container configuration. * * @param config * Current serlvet configuration passed by the container. * * @return The underlying template engine or null 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. *

* 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. // File file = super.getScriptUriAsFile(request); String 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()); } // // Get the requested template. // long getMillis = System.currentTimeMillis(); Template template = getTemplate(file); 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); 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) { StringBuffer sb = new StringBuffer(100); sb.append("\n\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]"); } } /** * Override this method to set your variables to the Groovy binding. *

* All variables bound the binding are passed to the template source text, * e.g. the HTML file, when the template is merged. *

*

* The binding provided by TemplateServlet does already include some default * variables. As of this writing, they are (copied from * {@link groovy.servlet.ServletBinding}): *

    *
  • "request" : HttpServletRequest
  • *
  • "response" : HttpServletResponse
  • *
  • "context" : ServletContext
  • *
  • "application" : ServletContext
  • *
  • "session" : request.getSession(false)
  • *
*

*

* And via implicite hard-coded keywords: *

    *
  • "out" : response.getWriter()
  • *
  • "sout" : response.getOutputStream()
  • *
  • "html" : new MarkupBuilder(response.getWriter())
  • *
*

* *

Example binding all servlet context variables: *


     * class Mytlet extends TemplateServlet {
     * 
     *   protected void setVariables(ServletBinding binding) {
     *     // Bind a simple variable
     *     binding.setVariable("answer", new Long(42));
     *   
     *     // Bind all servlet context attributes...
     *     ServletContext context = (ServletContext) binding.getVariable("context");
     *     Enumeration enumeration = context.getAttributeNames();
     *     while (enumeration.hasMoreElements()) {
     *       String name = (String) enumeration.nextElement();
     *       binding.setVariable(name, context.getAttribute(name));
     *     }
     *   }
     * 
     * }
     * 
*

* * @param binding * to be modified */ protected void setVariables(ServletBinding binding) { // empty } }