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: 5.0.0-alpha-11
Show newest version
/*
 *  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 javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Date;
import java.util.Map;
import java.util.WeakHashMap;

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

*

Servlet Init Parameters

*

*

Logging and extra-output options

*

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

 *   generated.by = true(default) | false
 * 
*

*

Groovy Source Encoding Parameter

*

* The following servlet init parameter name can be used to specify the encoding TemplateServlet will use * to read the template groovy source files: *

 *   groovy.source.encoding
 * 
* * @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. */ private static class TemplateCacheEntry { final Date date; long hit; long lastModified; long length; final Template template; 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 true if all measured values match, else false */ 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; } @Override 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; private String fileEncodingParamVal; private static final String GROOVY_SOURCE_ENCODING = "groovy.source.encoding"; /** * Create new TemplateServlet. */ public TemplateServlet() { this.cache = new WeakHashMap(); 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 File 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 recompilation! " + 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 inputStream an InputStream 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. * *

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

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

* 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) */ @Override 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. *

* 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 servlet 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 Class.forName(name).asSubclass(TemplateEngine.class).getDeclaredConstructor().newInstance(); } catch (InstantiationException | InvocationTargetException e) { log("Could not instantiate template engine: " + name, e); } catch (IllegalAccessException e) { log("Could not access template engine class: " + name, e); } catch (ClassNotFoundException | NoSuchMethodException 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 */ @Override 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 = 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 = getScriptUri(request); URL url = servletContext.getResource(name); if (url == null) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } 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) { String sb = "\n\n"; out.write(sb); } // // flush the response buffer. // response.flushBuffer(); if (verbose) { log("Template \"" + name + "\" request responded. [create/get=" + getMillis + " ms, make=" + makeMillis + " ms]"); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy