Package groovy.servlet

Source Code of groovy.servlet.TemplateServlet$TemplateCacheEntry

/*
* 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.
*
* <p>
* It delegates work to a <code>groovy.text.TemplateEngine</code> implementation
* processing HTTP requests.
*
* <h4>Usage</h4>
*
* <code>helloworld.html</code> is a headless HTML-like template
* <pre><code>
*  &lt;html&gt;
*    &lt;body&gt;
*      &lt;% 3.times { %&gt;
*        Hello World!
*      &lt;% } %&gt;
*      &lt;br&gt;
*    &lt;/body&gt;
*  &lt;/html&gt;
* </code></pre>
*
* Minimal <code>web.xml</code> example serving HTML-like templates
* <pre><code>
* &lt;web-app&gt;
*   &lt;servlet&gt;
*     &lt;servlet-name&gt;template&lt;/servlet-name&gt;
*     &lt;servlet-class&gt;groovy.servlet.TemplateServlet&lt;/servlet-class&gt;
*   &lt;/servlet&gt;
*   &lt;servlet-mapping&gt;
*     &lt;servlet-name&gt;template&lt;/servlet-name&gt;
*     &lt;url-pattern&gt;*.html&lt;/url-pattern&gt;
*   &lt;/servlet-mapping&gt;
* &lt;/web-app&gt;
* </code></pre>
*
* <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>
*
* <h4>Logging and extra-output options</h4>
*
* <p>
* This implementation provides a verbosity flag switching log statements.
* The servlet init parameter name is:
* <pre>
*   generate.by = true(default) | false
* </pre>
*
* @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 <code>true</code> if all measured values match, else <code>false</code>
         */
        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.
     *
     * <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.
     * </p>
     *
     * @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.
     * <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).booleanValue();
        }
        log("Servlet " + getClass().getName() + " initialized on " + engine.getClass());
    }

    /**
     * Creates the template engine.
     *
     * 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 serlvet 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.
     *
     * </p>
     * @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+"; 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) {
            StringBuffer sb = new StringBuffer(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]");
        }

    }

    /**
     * Override this method to set your variables to the Groovy binding.
     * <p>
     * All variables bound the binding are passed to the template source text,
     * e.g. the HTML file, when the template is merged.
     * </p>
     * <p>
     * The binding provided by TemplateServlet does already include some default
     * variables. As of this writing, they are (copied from
     * {@link groovy.servlet.ServletBinding}):
     * <ul>
     * <li><tt>"request"</tt> : HttpServletRequest </li>
     * <li><tt>"response"</tt> : HttpServletResponse </li>
     * <li><tt>"context"</tt> : ServletContext </li>
     * <li><tt>"application"</tt> : ServletContext </li>
     * <li><tt>"session"</tt> : request.getSession(<b>false</b>) </li>
     * </ul>
     * </p>
     * <p>
     * And via implicite hard-coded keywords:
     * <ul>
     * <li><tt>"out"</tt> : response.getWriter() </li>
     * <li><tt>"sout"</tt> : response.getOutputStream() </li>
     * <li><tt>"html"</tt> : new MarkupBuilder(response.getWriter()) </li>
     * </ul>
     * </p>
     *
     * <p>Example binding all servlet context variables:
     * <pre><code>
     * 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));
     *     }
     *   }
     *
     * }
     * <code></pre>
     * </p>
     *
     * @param binding
     *  to be modified
     */
    protected void setVariables(ServletBinding binding) {
        // empty
    }

}
TOP

Related Classes of groovy.servlet.TemplateServlet$TemplateCacheEntry

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.