Package org.thymeleaf

Source Code of org.thymeleaf.TemplateEngine

/*
* =============================================================================
*
*   Copyright (c) 2011-2014, The THYMELEAF team (http://www.thymeleaf.org)
*
*   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 org.thymeleaf;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thymeleaf.cache.ICacheManager;
import org.thymeleaf.cache.StandardCacheManager;
import org.thymeleaf.context.DialectAwareProcessingContext;
import org.thymeleaf.context.IContext;
import org.thymeleaf.context.IProcessingContext;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.dom.Document;
import org.thymeleaf.dom.Node;
import org.thymeleaf.exceptions.ConfigurationException;
import org.thymeleaf.exceptions.NotInitializedException;
import org.thymeleaf.exceptions.TemplateEngineException;
import org.thymeleaf.exceptions.TemplateOutputException;
import org.thymeleaf.exceptions.TemplateProcessingException;
import org.thymeleaf.fragment.IFragmentSpec;
import org.thymeleaf.messageresolver.IMessageResolver;
import org.thymeleaf.messageresolver.StandardMessageResolver;
import org.thymeleaf.templatemode.ITemplateModeHandler;
import org.thymeleaf.templatemode.StandardTemplateModeHandlers;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolution;
import org.thymeleaf.templatewriter.ITemplateWriter;
import org.thymeleaf.util.Validate;


/**
* <p>
*   Main class for the execution of templates.
* </p>
* <p>
*   In order to execute Thymeleaf templates, an instance of this class (or one of
*   its subclasses) must be created.
* </p>
*
* <h3>Creating an instance of <tt>TemplateEngine</tt></h3>
* <p>
*   An instance of this class can be created at any time by calling its constructor:
* </p>
* <code>
*   final TemplateEngine templateEngine = new TemplateEngine();
* </code>
* <p>
*   Creation and configuration of <tt>TemplateEngine</tt> instances is expensive, so it is
*   recommended to create only one instance of this class (or at least one instance per
*   dialect/configuration) and use it to process multiple templates.
* </p>
*
* <h3>Configuring the <tt>TemplateEngine</tt></h3>
* <p>
*   Once created, an instance of <tt>TemplateEngine</tt> has to be configured by
*   setting the following <b>required</b> parameters:
* </p>
* <ul>
*   <li>One or more <b>Template Resolvers</b> (instances of {@link ITemplateResolver}), in
*       charge of reading or obtaining the templates so that the engine is able to process them. If
*       only one template resolver is set (the most common case), the {@link #setTemplateResolver(ITemplateResolver)}
*       method can be used for this. If more resolvers are to be set, both the
*       {@link #setTemplateResolvers(Set)} and {@link #addTemplateResolver(ITemplateResolver)} methods
*       can be used.</li>
* </ul>
* <p>
*   Also, the following parameters can be optionally set:
* </p>
* <ul>
*   <li>One or more <b>Dialects</b> (instances of {@link IDialect}), defining the way in which templates
*       will be processed: DOM processors, expression parsers, etc. If no
*       dialect is explicitly set, a unique instance of {@link org.thymeleaf.standard.StandardDialect}
*       (the <i>Standard Dialect</i>) will be used.
*       <ul>
*         <li>Dialects define a <i>default prefix</i>, which will be used for them if not otherwise specified.</li>
*         <li>When setting/adding dialects, a non-default prefix can be specified for each of them.</li>
*         <li>Several dialects can use the same prefix, effectively acting as an aggregate dialect.</li>
*         <li>All specified dialects will be validated to ensure no conflicts with DOCTYPE translations or resolution entries
*             exist. Dialects defining a DOCTYPE translation or resolution entry <i>equal</i> to another one in a
*             different dialect are not considered to be in conflict.</li>
*         <li>Dialect leniency will be computed per-prefix, so that a prefix will be considered to be <i>lenient</i>
*             if at least one of the dialects configured for it is lenient.</li>
*         <li>Note that defining a non-default prefix for a dialect might affect its validation features
*             if this dialect includes DTD files for such purpose (e.g. the Standard Dialect).</li>
*       </ul>
*   </li>
*   <li>One or more <b>Message Resolvers</b> (instances of {@link IMessageResolver}), in
*       charge of resolving externalized messages. If no message resolver is explicitly set, the default
*       setting specified by {@link #setDefaultMessageResolvers(Set)} will be applied (this
*       default setting defaults itself to a single instance of {@link StandardMessageResolver}).
*       If only one message resolver is set, the {@link #setMessageResolver(IMessageResolver)} method
*       can be used for this. If more resolvers are to be set, both the
*       {@link #setMessageResolvers(Set)} and {@link #addMessageResolver(IMessageResolver)} methods
*       can be used.</li>
*   <li>A set of <b>Template Mode Handlers</b> (instances of {@link ITemplateModeHandler}, which will
*       take care of reading/parsing templates and also writing the results of processing them for a
*       specific template mode. The presence of these template mode handlers defines which are the valid
*       values for the <tt>templateMode</tt> attribute of template resolution results
*       ({@link TemplateResolution#getTemplateMode()}). If not explicitly set, template mode handlers
*       will be initialized to {@link StandardTemplateModeHandlers#ALL_TEMPLATE_MODE_HANDLERS}.
*   <li>A <b>Cache Manager</b> (instance of {@link ICacheManager}. The Cache Manager is in charge of
*       providing the cache objects (instances of {@link org.thymeleaf.cache.ICache}) to be used for
*       caching (at least) templates, fragments, messages and expressions. By default, a
*       {@link StandardCacheManager} instance is used. If a null cache manager is specified by calling
*       {@link #setCacheManager(ICacheManager)}, no caches will be used throughout the system at all.</li>
* </ul>
*
* <h3>Template Execution</h3>
* <h4>1. Creating a context</h4>
* <p>
*   All template executions require a <i>context</i>. A context is an object that
*   implements the {@link IContext} interface, and that contains at least the following
*   data:
* </p>
* <ul>
*   <li>The <i>locale</i> to be used for message externalization (internationalization).</li>
*   <li>The <i>context variables</i>. A map of variables that will be available for
*       use from expressions in the executed template.</li> 
* </ul>
* <p>
*   Two {@link IContext} implementations are provided out-of-the-box:
* </p>
* <ul>
*   <li>{@link org.thymeleaf.context.Context}, a standard implementation containing only
*       the required data.</li>
*   <li>{@link org.thymeleaf.context.WebContext}, a web-specific implementation
*       extending the {@link org.thymeleaf.context.IWebContext} subinterface, offering
*       request, session and servletcontext (application) attributes in special variables
*       inside the <i>context variables</i> map. Using an implementation of
*       {@link org.thymeleaf.context.IWebContext} is required when using Thymeleaf for
*       generating HTML/XHTML interfaces in web applications.</li>
* </ul>
* <p>
*   Creating a {@link org.thymeleaf.context.Context} instance is very simple:
* </p>
* <code>
*   final IContext ctx = new Context();<br />
*   ctx.setVariable("allItems", items);
* </code>
* <p>
*   A {@link org.thymeleaf.context.WebContext} would also need
*   {@link javax.servlet.http.HttpServletRequest} and
*   {@link javax.servlet.ServletContext} objects as constructor arguments:
* </p>
* <code>
*   final IContext ctx = new WebContext(request, servletContext);<br />
*   ctx.setVariable("allItems", items);
* </code>
* <p>
*   See the documentation for these specific implementations for more details.
* </p>
*
* <h4>2. Template Processing</h4>
* <p>
*   In order to execute templates, the {@link #process(String, IContext)} and
*   {@link #process(String, IContext, Writer)} methods can be used:
* </p>
* <p>
*   Without a writer, the processing result will be returned as a String:
* </p>
* <code>
*   final String result = templateEngine.process("mytemplate", ctx);
* </code>
* <p>
*   By specifying a writer, we can avoid the creation of a String containing the
*   whole processing result by writing this result into the output stream as soon
*   as it is produced from the processed DOM. This is specially useful in web
*   scenarios:
* </p>
* <code>
*   templateEngine.process("mytemplate", ctx, httpServletResponse.getWriter());
* </code>
* <p>
*   The <tt>"mytemplate"</tt> String argument is the <i>template name</i>, and it
*   will relate to the physical/logical location of the template itself in a way
*   configured at the template resolver/s.
* </p>
*
* @author Daniel Fern&aacute;ndez
*
* @since 1.0
*
*/
public class TemplateEngine {

    /**
     * <p>
     *   Name of the <tt>TIMER</tt> logger. This logger will output the time required
     *   for executing each template processing operation.
     * </p>
     * <p>
     *   The value of this constant is <tt>org.thymeleaf.TemplateEngine.TIMER</tt>. This
     *   allows you to set a specific configuration and/or appenders for timing info at your logging
     *   system configuration.
     * </p>
     */
    public static final String TIMER_LOGGER_NAME = TemplateEngine.class.getName() + ".TIMER";

    private static final Logger logger = LoggerFactory.getLogger(TemplateEngine.class);
    private static final Logger timerLogger = LoggerFactory.getLogger(TIMER_LOGGER_NAME);

    private static final int NANOS_IN_SECOND = 1000000;

   
    private final Configuration configuration;
    private TemplateRepository templateRepository;

    private volatile boolean initialized;
   


   

    /**
     * <p>
     *   Constructor for <tt>TemplateEngine</tt> objects.
     * </p>
     * <p>
     *   This is the only way to create a <tt>TemplateEngine</tt> instance (which
     *   should be configured after creation).
     * </p>
     */
    public TemplateEngine() {
        super();
        this.configuration = new Configuration();
        this.initialized = false;
        setCacheManager(new StandardCacheManager());
        setDefaultMessageResolvers(Collections.singleton(new StandardMessageResolver()));
        setDefaultTemplateModeHandlers(StandardTemplateModeHandlers.ALL_TEMPLATE_MODE_HANDLERS);
    }

   
   


    /**
     * <p>
     *   Checks whether the <tt>TemplateEngine</tt> has already been initialized
     *   or not. A <tt>TemplateEngine</tt> is initialized when the {@link #initialize()}
     *   method is called the first time a template is processed.
     * </p>
     * <p>
     *   Normally, there is no good reason why users would need to call this method.
     * </p>
     *
     * @return <tt>true</tt> if the template engine has already been initialized,
     *         <tt>false</tt> if not.
     */
    public final boolean isInitialized() {
        return this.initialized;
    }


   
    /**
     * <p>
     *   Returns the configuration object.
     * </p>
     *
     * @return the current configuration
     */
    public Configuration getConfiguration() {
        return this.configuration;
    }
   
   
    /**
     * <p>
     *   Returns the template repository. Normally there is no reason why users
     *   would want to obtain or use this object directly (and it is not recommended
     *   behaviour).
     * </p>
     *
     * @return the template repository
     */
    public TemplateRepository getTemplateRepository() {
        if (!isInitialized()) {
            throw new NotInitializedException("Template Engine has not been initialized");
        }
        return this.templateRepository;
    }

   
    /**
     * <p>
     *   Returns the configured dialects, referenced by their prefixes.
     * </p>
     *
     * @return the {@link IDialect} instances currently configured.
     */
    public final Map<String,IDialect> getDialectsByPrefix() {
        return this.configuration.getDialects();
    }
   
   
    /**
     * <p>
     *   Returns the configured dialects.
     * </p>
     *
     * @return the {@link IDialect} instances currently configured.
     */
    public final Set<IDialect> getDialects() {
        return this.configuration.getDialectSet();
    }

    /**
     * <p>
     *   Sets a new unique dialect for this template engine.
     * </p>
     * <p>
     *   This operation is equivalent to removing all the currently configured dialects and then
     *   adding this one.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param dialect the new unique {@link IDialect} to be used.
     */
    public void setDialect(final IDialect dialect) {
        this.configuration.setDialect(dialect);
    }

    /**
     * <p>
     *   Adds a new dialect for this template engine, using the specified prefix.
     * </p>
     * <p>
     *   This dialect will be added to the set of currently configured ones.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param prefix the prefix that will be used for this dialect
     * @param dialect the new {@link IDialect} to be added to the existing ones.
     */
    public void addDialect(final String prefix, final IDialect dialect) {
        this.configuration.addDialect(prefix, dialect);
    }

    /**
     * <p>
     *   Adds a new dialect for this template engine, using the dialect's specified
     *   default dialect.
     * </p>
     * <p>
     *   This dialect will be added to the set of currently configured ones.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param dialect the new {@link IDialect} to be added to the existing ones.
     */
    public void addDialect(final IDialect dialect) {
        this.configuration.addDialect(dialect.getPrefix(), dialect);
    }

    /**
     * <p>
     *   Sets a new set of dialects for this template engine, referenced
     *   by the prefixes they will be using.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param dialects the new map of {@link IDialect} objects to be used, referenced
     *        by their prefixes.
     */
    public void setDialectsByPrefix(final Map<String,IDialect> dialects) {
        this.configuration.setDialects(dialects);
    }

    /**
     * <p>
     *   Sets a new set of dialects for this template engine, all of them using
     *   their default prefixes.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param dialects the new set of {@link IDialect} objects to be used.
     */
    public void setDialects(final Set<IDialect> dialects) {
        Validate.notNull(dialects, "Dialect set cannot be null");
        final Map<String,IDialect> dialectMap = new LinkedHashMap<String, IDialect>(dialects.size() + 2, 1.0f);
        for (final IDialect dialect : dialects)  {
            dialectMap.put(dialect.getPrefix(), dialect);
        }
        this.configuration.setDialects(dialectMap);
    }

   
    /**
     * <p>
     *   Sets an additional set of dialects for this template engine, all of them using
     *   their default prefixes.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param additionalDialects the new set of {@link IDialect} objects to be used.
     *
     * @since 2.0.9
     *
     */
    public void setAdditionalDialects(final Set<IDialect> additionalDialects) {
        Validate.notNull(additionalDialects, "Additional dialect set cannot be null");
        final Map<String,IDialect> dialectMap = new LinkedHashMap<String, IDialect>(5, 1.0f);
        dialectMap.putAll(this.configuration.getDialects());
        for (final IDialect dialect : additionalDialects)  {
            dialectMap.put(dialect.getPrefix(), dialect);
        }
        this.configuration.setDialects(dialectMap);
    }

   
    /**
     * <p>
     *   Removes all the currently configured dialects.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     */
    public void clearDialects() {
        this.configuration.clearDialects();
    }

   

    /**
     * <p>
     *   Returns the Set of template resolvers currently configured.
     * </p>
     *
     * @return the template resolvers.
     */
    public final Set<ITemplateResolver> getTemplateResolvers() {
        return this.configuration.getTemplateResolvers();
    }

    /**
     * <p>
     *   Sets the entire set of template resolvers.
     * </p>
     *
     * @param templateResolvers the new template resolvers.
     */
    public void setTemplateResolvers(final Set<? extends ITemplateResolver> templateResolvers) {
        this.configuration.setTemplateResolvers(templateResolvers);
    }

    /**
     * <p>
     *   Adds a new template resolver to the current set.
     * </p>
     *
     * @param templateResolver the new template resolver.
     */
    public void addTemplateResolver(final ITemplateResolver templateResolver) {
        this.configuration.addTemplateResolver(templateResolver);
    }

    /**
     * <p>
     *   Sets a single template resolver for this template engine.
     * </p>
     * <p>
     *   Calling this method is equivalent to calling {@link #setTemplateResolvers(Set)}
     *   passing a Set with only one template resolver.
     * </p>
     *
     * @param templateResolver the template resolver to be set.
     */
    public void setTemplateResolver(final ITemplateResolver templateResolver) {
        this.configuration.setTemplateResolver(templateResolver);
    }

   
    /**
     * <p>
     *   Returns the cache manager in effect. This manager is in charge of providing
     *   the various caches needed by the system during its process.
     * </p>
     * <p>
     *   By default, an instance of {@link org.thymeleaf.cache.StandardCacheManager}
     *   is set.
     * </p>
     *
     * @return the cache manager
     */
    public ICacheManager getCacheManager() {
        return this.configuration.getCacheManager();
    }
   
    /**
     * <p>
     *   Sets the Cache Manager to be used. If set to null, no caches will be used
     *   throughout the engine.
     * </p>
     * <p>
     *   By default, an instance of {@link org.thymeleaf.cache.StandardCacheManager}
     *   is set.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param cacheManager the cache manager to be set.
     *
     */
    public void setCacheManager(final ICacheManager cacheManager) {
        // Can be set to null (= no caches at all)
        this.configuration.setCacheManager(cacheManager);
    }

   
    /**
     * <p>
     *   Returns the set of Message Resolvers configured for this Template Engine.
     * </p>
     *
     * @return the set of message resolvers.
     */
    public final Set<IMessageResolver> getMessageResolvers() {
        return this.configuration.getMessageResolvers();
    }

    /**
     * <p>
     *   Sets the message resolvers to be used by this template engine.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param messageResolvers the Set of template resolvers.
     */
    public void setMessageResolvers(final Set<? extends IMessageResolver> messageResolvers) {
        this.configuration.setMessageResolvers(messageResolvers);
    }
   
    /**
     * <p>
     *   Adds a message resolver to the set of message resolvers to be used
     *   by the template engine.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param messageResolver the new message resolver to be added.
     */
    public void addMessageResolver(final IMessageResolver messageResolver) {
        this.configuration.addMessageResolver(messageResolver);
    }

    /**
     * <p>
     *   Sets a single message resolver for this template engine.
     * </p>
     * <p>
     *   Calling this method is equivalent to calling {@link #setMessageResolvers(Set)}
     *   passing a Set with only one message resolver.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param messageResolver the message resolver to be set.
     */
    public void setMessageResolver(final IMessageResolver messageResolver) {
        this.configuration.setMessageResolver(messageResolver);
    }

    /**
     * <p>
     *   Sets the default message resolvers. These are used when no message resolvers
     *   are set via the {@link #setMessageResolver(IMessageResolver)},
     *   {@link #setMessageResolvers(Set)} or {@link #addMessageResolver(IMessageResolver)}
     *   methods.
     * </p>
     * <p>
     *   This method is useful for creating subclasses of <tt>TemplateEngine</tt> that
     *   establish default configurations for message resolvers.
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param defaultMessageResolvers the default message resolvers.
     */
    public void setDefaultMessageResolvers(final Set<? extends IMessageResolver> defaultMessageResolvers) {
        this.configuration.setDefaultMessageResolvers(defaultMessageResolvers);
    }

   
    /**
     * <p>
     *   Returns the set of Template Mode Handlers configured for this
     *   Template Engine.
     * </p>
     * <p>
     *   By default, template mode handlers set are
     *   {@link StandardTemplateModeHandlers#ALL_TEMPLATE_MODE_HANDLERS}
     * </p>
     *
     * @return the set of Template Mode Handlers.
     */
    public final Set<ITemplateModeHandler> getTemplateModeHandlers() {
        return this.configuration.getTemplateModeHandlers();
    }

    /**
     * <p>
     *   Sets the Template Mode Handlers to be used by this template engine.
     *   Every available template mode must have its corresponding handler.
     * </p>
     * <p>
     *   By default, template mode handlers set are
     *   {@link StandardTemplateModeHandlers#ALL_TEMPLATE_MODE_HANDLERS}
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param templateModeHandlers the Set of Template Mode Handlers.
     */
    public void setTemplateModeHandlers(final Set<? extends ITemplateModeHandler> templateModeHandlers) {
        this.configuration.setTemplateModeHandlers(templateModeHandlers);
    }
   
    /**
     * <p>
     *   Adds a Template Mode Handler to the set of Template Mode Handlers to be used
     *   by the template engine.
     *   Every available template mode must have its corresponding handler.
     * </p>
     * <p>
     *   By default, template mode handlers set are
     *   {@link StandardTemplateModeHandlers#ALL_TEMPLATE_MODE_HANDLERS}
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param templateModeHandler the new Template Mode Handler to be added.
     */
    public void addTemplateModeHandler(final ITemplateModeHandler templateModeHandler) {
        this.configuration.addTemplateModeHandler(templateModeHandler);
    }

    /**
     * <p>
     *   Sets the default Template Mode Handlers. These are used when no Template Mode Handlers
     *   are set via the {@link #setTemplateModeHandlers(Set)} or
     *   {@link #addTemplateModeHandler(ITemplateModeHandler)} methods.
     * </p>
     * <p>
     *   This method is useful for creating subclasses of <tt>TemplateEngine</tt> that
     *   establish default configurations for Template Mode Handlers.
     * </p>
     * <p>
     *   By default, template mode handlers set are
     *   {@link StandardTemplateModeHandlers#ALL_TEMPLATE_MODE_HANDLERS}
     * </p>
     * <p>
     *   This operation can only be executed before processing templates for the first
     *   time. Once a template is processed, the template engine is considered to be
     *   <i>initialized</i>, and from then on any attempt to change its configuration
     *   will result in an exception.
     * </p>
     *
     * @param defaultTemplateModeHandlers the default Template Mode Handlers.
     */
    public void setDefaultTemplateModeHandlers(final Set<? extends ITemplateModeHandler> defaultTemplateModeHandlers) {
        this.configuration.setDefaultTemplateModeHandlers(defaultTemplateModeHandlers);
    }
   
   
   
   

   
   
    /**
     * <p>
     *   Completely clears the Template Cache.
     * </p>
     * <p>
     *   If this method is called before the TemplateEngine has been initialized,
     *   it causes its initialization.
     * </p>
     */
    public void clearTemplateCache() {
        if (!isInitialized()) {
            initialize();
        }
        this.templateRepository.clearTemplateCache();
    }


    /**
     * <p>
     *   Clears the entry in the Template Cache for the specified
     *   template, if it is currently cached.
     * </p>
     * <p>
     *   If this method is called before the TemplateEngine has been initialized,
     *   it causes its initialization.
     * </p>
     *
     * @param templateName the name of the template to be cleared from cache.
     */
    public void clearTemplateCacheFor(final String templateName) {
        Validate.notNull(templateName, "Template name cannot be null");
        if (!isInitialized()) {
            initialize();
        }
        this.templateRepository.clearTemplateCacheFor(templateName);
    }
   
   
   
   
   
   
   
    /**
     * <p>
     *   Internal method that initializes the Template Engine instance. This method
     *   is called before the first execution of {@link #process(String, IContext)}
     *   in order to create all the structures required for a quick execution of
     *   templates.
     * </p>
     * <p>
     *   THIS METHOD IS INTERNAL AND SHOULD <b>NEVER</b> BE CALLED DIRECTLY.
     * </p>
     * <p>
     *   If a subclass of <tt>TemplateEngine</tt> needs additional steps for
     *   initialization, the {@link #initializeSpecific()} method should
     *   be overridden.
     * </p>
     */
    public final synchronized void initialize() {
       
        if (!isInitialized()) {
           
            logger.info("[THYMELEAF] INITIALIZING TEMPLATE ENGINE");

            this.configuration.initialize();
           
            this.templateRepository = new TemplateRepository(this.configuration);
           
            initializeSpecific();
           
            this.initialized = true;

            // Log configuration details
            this.configuration.printConfiguration();
           
            logger.info("[THYMELEAF] TEMPLATE ENGINE INITIALIZED");
           
        }
       
    }
   

    /**
     * <p>
     *   This method performs additional initializations required for a
     *   <tt>TemplateEngine</tt>. It is called by {@link #initialize()}.
     * </p>
     * <p>
     *   The implementation of this method does nothing, and it is designed
     *   for being overridden by subclasses of <tt>TemplateEngine</tt>.
     * </p>
     */
    protected void initializeSpecific() {
        // Nothing to be executed here. Meant for extension
    }
   
   
   
    /**
     * <p>
     *   Internal method that retrieves the thread name/index for the
     *   current template execution.
     * </p>
     * <p>
     *   THIS METHOD IS INTERNAL AND SHOULD <b>NEVER</b> BE CALLED DIRECTLY.
     * </p>
     *
     * @return the index of the current execution.
     */
    public static String threadIndex() {
        return Thread.currentThread().getName();
    }

   
   
   
    /**
     * <p>
     *   Process a template. This method receives both a <i>template name</i> and a <i>context</i>.
     * </p>
     * <p>
     *   The template name will be used as input for the template resolvers, queried in chain
     *   until one of them resolves the template, which will then be executed.
     * </p>
     * <p>
     *   The context will contain the variables that will be available for the execution of
     *   expressions inside the template.
     * </p>
     *
     * @param templateName the name of the template.
     * @param context the context.
     * @return a String containing the result of evaluating the specified template
     *         with the provided context.
     */
    public final String process(final String templateName, final IContext context) {
        return process(templateName, context, (IFragmentSpec)null);
    }


   

    /**
     * <p>
     *   Process a template. This method receives both a <i>template name</i>, a <i>context</i>
     *   and a <i>fragment specification</i> ({@link IFragmentSpec}). This
     *   method works essentially the same as {@link #process(String, IContext)} but
     *   applying the specified fragment specification as a filter on the parsed template in order to
     *   process only a fragment of such template.
     * </p>
     *
     * @param templateName the name of the template.
     * @param context the context.
     * @param fragmentSpec the fragment specification that will be applied as a filter to the parsed
     *                     template, before processing.
     * @return a String containing the result of evaluating the specified template
     *         with the provided context.
     *
     * @since 2.0.9
     */
    public final String process(final String templateName, final IContext context, final IFragmentSpec fragmentSpec) {
        final StringWriter stringWriter = new StringWriter();
        process(templateName, context, fragmentSpec, stringWriter);
        return stringWriter.toString();
    }
   

   

    /**
     * <p>
     *   Process a template. This method receives a <i>template name</i>, a <i>context</i> and
     *   also a {@link Writer}, so that there is no need to create a String object containing the
     *   whole processing results because these will be written to the specified writer as
     *   soon as they are generated from the processed DOM tree. This is specially useful for
     *   web environments (using {@link javax.servlet.http.HttpServletResponse#getWriter()}).
     * </p>
     * <p>
     *   The template name will be used as input for the template resolvers, queried in chain
     *   until one of them resolves the template, which will then be executed.
     * </p>
     * <p>
     *   The context will contain the variables that will be available for the execution of
     *   expressions inside the template.
     * </p>
     *
     * @param templateName the name of the template.
     * @param context the context.
     * @param writer the writer the results will be output to.
     *
     * @since 2.0.0
     */
    public final void process(final String templateName, final IContext context, final Writer writer) {
        process(templateName, context, null, writer);
    }
   

   

    /**
     * <p>
     *   Process a template. This method receives a <i>template name</i>, a <i>processing context</i>
     *   (which might include local variables) and also a {@link Writer}, so that there is no need
     *   to create a String object containing the whole processing results because these will be
     *   written to the specified writer as soon as they are generated from the processed DOM
     *   tree. This is specially useful for web environments
     *   (using {@link javax.servlet.http.HttpServletResponse#getWriter()}).
     * </p>
     * <p>
     *   The template name will be used as input for the template resolvers, queried in chain
     *   until one of them resolves the template, which will then be executed.
     * </p>
     * <p>
     *   The context will contain the variables that will be available for the execution of
     *   expressions inside the template.
     * </p>
     *
     * @param templateName the name of the template.
     * @param processingContext the processing context.
     * @param writer the writer the results will be output to.
     *
     * @since 2.0.0
     */
    public final void process(final String templateName, final IProcessingContext processingContext, final Writer writer) {
        process(templateName, processingContext, null, writer);
    }
   

   

    /**
     * <p>
     *   Process a template. This method receives a <i>template name</i>, a <i>context</i>, a
     *   <i>fragment specification</i> ({@link IFragmentSpec}) and also a {@link Writer}. This
     *   method works essentially the same as {@link #process(String, IContext, Writer)} but
     *   applying the specified fragment specification as a filter on the parsed template in order to
     *   process only a fragment of such template.
     * </p>
     *
     * @param templateName the name of the template.
     * @param context the context.
     * @param fragmentSpec the fragment specification that will be applied as a filter to the parsed
     *                     template, before processing.
     * @param writer the writer the results will be output to.
     *
     * @since 2.0.9
     */
    public final void process(final String templateName, final IContext context,
            final IFragmentSpec fragmentSpec, final Writer writer) {
        process(templateName, new DialectAwareProcessingContext(context, getDialects()), fragmentSpec, writer);
    }
   

   

    /**
     * <p>
     *   Process a template. This method receives a <i>template name</i>, a <i>processing context</i>
     *   (which might include local variables), a <i>fragment specification</i> ({@link IFragmentSpec})
     *   and also a {@link Writer}. This method works essentially the same as
     *   {@link #process(String, IContext, Writer)} but applying the specified fragment specification
     *   as a filter on the parsed template in order to process only a fragment of such template.
     * </p>
     *
     * @param templateName the name of the template.
     * @param processingContext the processing context.
     * @param fragmentSpec the fragment specification that will be applied as a filter to the parsed
     *                     template, before processing.
     * @param writer the writer the results will be output to.
     *
     * @since 2.0.9
     */
    public final void process(final String templateName, final IProcessingContext processingContext,
            final IFragmentSpec fragmentSpec, final Writer writer) {
       
        if (!isInitialized()) {
            initialize();
        }
       
        try {
           
            Validate.notNull(templateName, "Template name cannot be null");
            Validate.notNull(processingContext, "Processing context cannot be null");
           
            final IContext context = processingContext.getContext();
           
            final long startNanos = System.nanoTime();

            if (logger.isDebugEnabled()) {
                logger.debug("[THYMELEAF][{}] STARTING PROCESS OF TEMPLATE \"{}\" WITH LOCALE {}", new Object[] {TemplateEngine.threadIndex(), templateName, context.getLocale()});
            }
           
            // Add context execution info
            context.addContextExecutionInfo(templateName);
           
            final TemplateProcessingParameters templateProcessingParameters =
                new TemplateProcessingParameters(this.configuration, templateName, processingContext);
           
            process(templateProcessingParameters, fragmentSpec, writer);
           
            final long endNanos = System.nanoTime();
           
            if (logger.isDebugEnabled()) {
                logger.debug("[THYMELEAF][{}] FINISHED PROCESS AND OUTPUT OF TEMPLATE \"{}\" WITH LOCALE {}", new Object[] {TemplateEngine.threadIndex(), templateName, context.getLocale()});
            }
           
            if (timerLogger.isDebugEnabled()) {
                final BigDecimal elapsed = BigDecimal.valueOf(endNanos - startNanos);
                final BigDecimal elapsedMs = elapsed.divide(BigDecimal.valueOf(NANOS_IN_SECOND), RoundingMode.HALF_UP);
                timerLogger.debug(
                        "[THYMELEAF][{}][{}][{}][{}][{}] TEMPLATE \"{}\" WITH LOCALE {} PROCESSED IN {} nanoseconds (approx. {}ms)",
                        new Object[] {TemplateEngine.threadIndex(),
                                templateName, context.getLocale(), elapsed, elapsedMs,
                                templateName, context.getLocale(), elapsed, elapsedMs});
            }
           
        } catch (final TemplateOutputException e) {
           
            logger.error("[THYMELEAF][{}] Exception processing template \"{}\": {}", new Object[] {TemplateEngine.threadIndex(), templateName, e.getMessage()});
            throw e;
           
        } catch (final TemplateEngineException e) {
           
            logger.error("[THYMELEAF][{}] Exception processing template \"{}\": {}", new Object[] {TemplateEngine.threadIndex(), templateName, e.getMessage()});
            throw e;
           
        } catch (final RuntimeException e) {
           
            logger.error("[THYMELEAF][{}] Exception processing template \"{}\": {}", new Object[] {TemplateEngine.threadIndex(), templateName, e.getMessage()});
            throw new TemplateProcessingException("Exception processing template", templateName, e);
           
        }
       
    }
   
   

    private void process(final TemplateProcessingParameters templateProcessingParameters,
            final IFragmentSpec fragmentSpec, final Writer writer) {
       
        final String templateName = templateProcessingParameters.getTemplateName();
       
        final Template template = this.templateRepository.getTemplate(templateProcessingParameters);
        final TemplateResolution templateResolution = template.getTemplateResolution();
        final String templateMode = templateResolution.getTemplateMode();

        Document document = template.getDocument();

        if (fragmentSpec != null) {

            // Apply the fragment specification and filter the parsed template.
            final List<Node> processingRootNodes =
                    fragmentSpec.extractFragment(this.configuration, Collections.singletonList((Node)document));
           
            if (processingRootNodes == null || processingRootNodes.size() == 0) {
                // If the result is null, there will be no processing to do
                document = null;
            } else {
                final Node firstProcessingRootNode = processingRootNodes.get(0);
                if (processingRootNodes.size() == 1 &&
                        firstProcessingRootNode != null &&
                        firstProcessingRootNode instanceof Document) {
                    // If it is a document, just process it as it is output from the filter
                    document = (Document) firstProcessingRootNode;
                } else {
                    // Fragment exists and it is not a Document. We will therefore lose DOCTYPE
                    final String documentName = document.getDocumentName();
                    document = new Document(documentName);
                    for (final Node processingRootNode : processingRootNodes) {
                        if (processingRootNode != null) {
                            final Node clonedProcessingRootNode =
                                    processingRootNode.cloneNode(document, false);
                            document.addChild(clonedProcessingRootNode);
                        }
                    }
                    document.precompute(this.configuration);
                }
            }
           
        }
       
        final Arguments arguments =
                new Arguments(this,
                        templateProcessingParameters, templateResolution,
                        this.templateRepository, document);
      
       
        if (logger.isDebugEnabled()) {
            logger.debug("[THYMELEAF][{}] Starting process on template \"{}\" using mode \"{}\"",
                    new Object[] { TemplateEngine.threadIndex(), templateName, templateMode });
        }

        if (document != null) {
            document.process(arguments);
        }
       
        if (logger.isDebugEnabled()) {
            logger.debug("[THYMELEAF][{}] Finished process on template \"{}\" using mode \"{}\"",
                    new Object[] { TemplateEngine.threadIndex(), templateName, templateMode });
        }
       
        final ITemplateModeHandler templateModeHandler =
                this.configuration.getTemplateModeHandler(templateMode);
        final ITemplateWriter templateWriter = templateModeHandler.getTemplateWriter();

        if (templateWriter == null) {
            throw new ConfigurationException(
                    "No template writer defined for template mode \"" + templateMode + "\"");
        }
       
        try {
            // It depends on the ITemplateWriter implementation to allow nulls or not.
            // Standard writer will simply not write anything for null.
            templateWriter.write(arguments, writer, document);
        } catch (IOException e) {
            throw new TemplateOutputException("Error during creation of output", e);
        }
   
    }

   
   
   
}
TOP

Related Classes of org.thymeleaf.TemplateEngine

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.