/* * ============================================================================= * * Copyright (c) 2011-2016, 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.Writer; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; 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.IContext; import org.thymeleaf.context.IEngineContextFactory; import org.thymeleaf.context.StandardEngineContextFactory; import org.thymeleaf.dialect.IDialect; import org.thymeleaf.engine.TemplateManager; import org.thymeleaf.exceptions.TemplateEngineException; import org.thymeleaf.exceptions.TemplateOutputException; import org.thymeleaf.exceptions.TemplateProcessingException; import org.thymeleaf.linkbuilder.ILinkBuilder; import org.thymeleaf.linkbuilder.StandardLinkBuilder; import org.thymeleaf.messageresolver.IMessageResolver; import org.thymeleaf.messageresolver.StandardMessageResolver; import org.thymeleaf.standard.StandardDialect; import org.thymeleaf.templateparser.markup.decoupled.IDecoupledTemplateLogicResolver; import org.thymeleaf.templateparser.markup.decoupled.StandardDecoupledTemplateLogicResolver; import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.StringTemplateResolver; import org.thymeleaf.util.FastStringWriter; import org.thymeleaf.util.LoggingUtils; import org.thymeleaf.util.Validate; /** * <p> * Main class for the execution of templates. * </p> * <p> * This is the only implementation of {@link ITemplateEngine} provided out of the box by Thymeleaf. * </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 typically configured a * mechanism for <em>resolving templates</em> (i.e. obtaining and reading them): * </p> * <ul> * <li>One or more <b>Template Resolvers</b> (implementations 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> * <li>If no <em>Template Resolvers</em> are configured, {@link TemplateEngine} instances will use * a {@link StringTemplateResolver} instance which will consider templates being specified * for processing as the template contents (instead of just their names). This configuration * will be overridden when {@link #setTemplateResolver(ITemplateResolver)}, {@link #setTemplateResolvers(Set)} or * {@link #addTemplateResolver(ITemplateResolver)} are called for the first time.</li> * </ul> * <p> * Templates will be processed according to a set of configured <strong>Dialects</strong> (implementations * of {@link IDialect}), defining the way in which templates will be processed: processors, expression objects, etc. * If no dialect is explicitly set, a unique instance of {@link org.thymeleaf.standard.StandardDialect} * (the <i>Standard Dialect</i>) will be used. * </p> * <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> * </ul> * <p> * Those templates that include any <em>externalized messages</em> (also <em>internationalized messages</em>, * i18n) will need the {@link TemplateEngine} to be configured one or more <b>Message Resolvers</b> (implementations * of {@link IMessageResolver}). If no message resolver is explicitly set, {@link TemplateEngine} will default * to a single instance of {@link StandardMessageResolver}). * </p> * <p> * 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. * </p> * <p> * Also, building link URLs from templates will need the configuration of at least one <em>link builder</em> * (implementation of {@link ILinkBuilder}) that will be in charge of giving shape to the URLs being output. * If no link builder is explicitly set, a {@link StandardLinkBuilder} will be set as a default implementation, * making use of the java Servlet API when it is needed for specific types of URLs. * </p> * <p> * Besides these, a <b>Cache Manager</b> (implementation of {@link ICacheManager}) can also be configured. 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) parsed templates and parsed 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. * </p> * * <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 * access to request, session and servletcontext (application) attributes in special * expression objects. Using an implementation of * {@link org.thymeleaf.context.IWebContext} is required when using Thymeleaf for * generating HTML interfaces in web applications based on the Servlet API.</li> * </ul> * <p> * Creating a {@link org.thymeleaf.context.Context} instance is very simple: * </p> * <code> * final Context ctx = new Context();<br> * ctx.setVariable("allItems", items); * </code> * <p> * If you want, you can also specify the locale to be used for processing the template: * </p> * <code> * final Context ctx = new Context(new Locale("gl","ES"));<br> * ctx.setVariable("allItems", items); * </code> * <p> * A {@link org.thymeleaf.context.WebContext} would also need * {@link javax.servlet.http.HttpServletRequest}, {@link javax.servlet.http.HttpServletResponse} and * {@link javax.servlet.ServletContext} objects as constructor arguments: * </p> * <code> * final IContext ctx = new WebContext(request, response, 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 different <tt>process(...)</tt> methods should * be used. Those are mostly divided into two blocks: those that return the template processing * result as a <tt>String</tt>, and those that receive a {@link Writer} as an argument * and use it for writing the result instead. * </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 template. This is especially useful (and highly * recommended) 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> * <hr> * <p> * Note a class with this name existed since 1.0, but it was completely reimplemented * in Thymeleaf 3.0 * </p> * * @author Daniel Fernández * * @since 3.0.0 * */ public class TemplateEngine implements ITemplateEngine { /** * <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 volatile boolean initialized = false; private final Set<DialectConfiguration> dialectConfigurations = new LinkedHashSet<DialectConfiguration>(3); private final Set<ITemplateResolver> templateResolvers = new LinkedHashSet<ITemplateResolver>(3); private final Set<IMessageResolver> messageResolvers = new LinkedHashSet<IMessageResolver>(3); private final Set<ILinkBuilder> linkBuilders = new LinkedHashSet<ILinkBuilder>(3); private ICacheManager cacheManager = null; private IEngineContextFactory engineContextFactory = null; private IDecoupledTemplateLogicResolver decoupledTemplateLogicResolver = null; private IEngineConfiguration configuration = null; /** * <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(); setCacheManager(new StandardCacheManager()); setEngineContextFactory(new StandardEngineContextFactory()); setMessageResolver(new StandardMessageResolver()); setLinkBuilder(new StandardLinkBuilder()); setDecoupledTemplateLogicResolver(new StandardDecoupledTemplateLogicResolver()); setDialect(new StandardDialect()); } private void checkNotInitialized() { if (this.initialized) { throw new IllegalStateException( "Template engine has already been initialized (probably because it has already been executed or " + "a fully-built Configuration object has been requested from it. At this state, no modifications on " + "its configuration are allowed."); } } /** * <p> * Internal method that initializes the Template Engine instance. This method * is called before the first execution of {@link TemplateEngine#process(String, IContext)} * or {@link TemplateEngine#processThrottled(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> */ final void initialize() { if (!this.initialized) { synchronized (this) { if (!this.initialized) { logger.debug("[THYMELEAF] INITIALIZING TEMPLATE ENGINE"); // First of all, give subclasses the opportunity to modify the base configuration initializeSpecific(); // We need at least one template resolver at this point - we set the StringTemplateResolver as default if (this.templateResolvers.isEmpty()) { this.templateResolvers.add(new StringTemplateResolver()); } // Build the EngineConfiguration object this.configuration = new EngineConfiguration( this.templateResolvers, this.messageResolvers, this.linkBuilders, this.dialectConfigurations, this.cacheManager, this.engineContextFactory, this.decoupledTemplateLogicResolver); ((EngineConfiguration)this.configuration).initialize(); this.initialized = true; // Log configuration details ConfigurationPrinterHelper.printConfiguration(this.configuration); logger.debug("[THYMELEAF] TEMPLATE ENGINE INITIALIZED"); } } } } /** * <p> * This method performs additional initializations required for a * <tt>TemplateEngine</tt> subclass instance. This method * is called before the first execution of * {@link TemplateEngine#process(String, org.thymeleaf.context.IContext)} * or {@link TemplateEngine#processThrottled(String, org.thymeleaf.context.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> * The base 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> * 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; } public IEngineConfiguration getConfiguration() { if (!this.initialized) { initialize(); } return this.configuration; } /** * <p> * Returns the configured dialects, referenced by their prefixes. * </p> * * @return the {@link IDialect} instances currently configured. * @since 3.0.0 */ public final Map<String,Set<IDialect>> getDialectsByPrefix() { final Set<DialectConfiguration> dialectConfs; if (this.initialized) { dialectConfs = this.configuration.getDialectConfigurations(); } else { dialectConfs = this.dialectConfigurations; } final Map<String,Set<IDialect>> dialectsByPrefix = new LinkedHashMap<String, Set<IDialect>>(3); for (final DialectConfiguration dialectConfiguration : dialectConfs) { final String prefix = dialectConfiguration.getPrefix(); Set<IDialect> dialectsForPrefix = dialectsByPrefix.get(prefix); if (dialectsForPrefix == null) { dialectsForPrefix = new LinkedHashSet<IDialect>(2); dialectsByPrefix.put(prefix, dialectsForPrefix); } dialectsForPrefix.add(dialectConfiguration.getDialect()); } return Collections.unmodifiableMap(dialectsByPrefix); } /** * <p> * Returns the configured dialects. * </p> * * @return the {@link IDialect} instances currently configured. */ public final Set<IDialect> getDialects() { if (this.initialized) { return this.configuration.getDialects(); } final Set<IDialect> dialects = new LinkedHashSet<IDialect>(this.dialectConfigurations.size()); for (final DialectConfiguration dialectConfiguration : this.dialectConfigurations) { dialects.add(dialectConfiguration.getDialect()); } return Collections.unmodifiableSet(dialects); } /** * <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) { Validate.notNull(dialect, "Dialect cannot be null"); checkNotInitialized(); this.dialectConfigurations.clear(); this.dialectConfigurations.add(new DialectConfiguration(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) { Validate.notNull(dialect, "Dialect cannot be null"); checkNotInitialized(); this.dialectConfigurations.add(new DialectConfiguration(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) { Validate.notNull(dialect, "Dialect cannot be null"); checkNotInitialized(); this.dialectConfigurations.add(new DialectConfiguration(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) { Validate.notNull(dialects, "Dialect map cannot be null"); checkNotInitialized(); this.dialectConfigurations.clear(); for (final Map.Entry<String,IDialect> dialectEntry : dialects.entrySet()) { addDialect(dialectEntry.getKey(), dialectEntry.getValue()); } } /** * <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"); checkNotInitialized(); this.dialectConfigurations.clear(); for (final IDialect dialect : dialects) { addDialect(dialect); } } /** * <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, "Dialect set cannot be null"); checkNotInitialized(); for (final IDialect dialect : additionalDialects) { addDialect(dialect); } } /** * <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() { checkNotInitialized(); this.dialectConfigurations.clear(); } /** * <p> * Returns the Set of template resolvers currently configured. * </p> * * @return the template resolvers. */ public final Set<ITemplateResolver> getTemplateResolvers() { if (this.initialized) { return this.configuration.getTemplateResolvers(); } return Collections.unmodifiableSet(this.templateResolvers); } /** * <p> * Sets the entire set of template resolvers. * </p> * * @param templateResolvers the new template resolvers. */ public void setTemplateResolvers(final Set<ITemplateResolver> templateResolvers) { Validate.notNull(templateResolvers, "Template Resolver set cannot be null"); checkNotInitialized(); this.templateResolvers.clear(); for (final ITemplateResolver templateResolver : templateResolvers) { addTemplateResolver(templateResolver); } } /** * <p> * Adds a new template resolver to the current set. * </p> * * @param templateResolver the new template resolver. */ public void addTemplateResolver(final ITemplateResolver templateResolver) { Validate.notNull(templateResolver, "Template Resolver cannot be null"); checkNotInitialized(); this.templateResolvers.add(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) { Validate.notNull(templateResolver, "Template Resolver cannot be null"); checkNotInitialized(); this.templateResolvers.clear(); this.templateResolvers.add(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 final ICacheManager getCacheManager() { if (this.initialized) { return this.configuration.getCacheManager(); } return this.cacheManager; } /** * <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) checkNotInitialized(); this.cacheManager = cacheManager; } /** * <p> * Returns the engine context factory in effect. This factory is responsible for creating the * (internally-used) instances of {@link org.thymeleaf.context.IEngineContext} that will support * template processing. * </p> * <p> * By default, an instance of {@link org.thymeleaf.context.StandardEngineContextFactory} * is set. * </p> * * @return the cache manager */ public final IEngineContextFactory getEngineContextFactory() { if (this.initialized) { return this.configuration.getEngineContextFactory(); } return this.engineContextFactory; } /** * <p> * Sets the Engine Context Factory (implementation of {@link IEngineContextFactory}) to be * used for template processing. * </p> * <p> * By default, an instance of {@link org.thymeleaf.context.StandardEngineContextFactory} * 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 engineContextFactory the engine context factory to be used. * */ public void setEngineContextFactory(final IEngineContextFactory engineContextFactory) { Validate.notNull(engineContextFactory, "Engine Context Factory cannot be set to null"); checkNotInitialized(); this.engineContextFactory = engineContextFactory; } /** * <p> * Returns the Decoupled Template Logic Resolver in effect. This resolver is responsible for obtaining * the resource containing the decoupled template logic to be added to templates that are marked during their * own resolution to need additional external logic (in order for them to be pure HTML/XML markup). * </p> * <p> * By default, an instance of {@link org.thymeleaf.templateparser.markup.decoupled.StandardDecoupledTemplateLogicResolver} * is set. * </p> * * @return the decoupled template logic resolver */ public final IDecoupledTemplateLogicResolver getDecoupledTemplateLogicResolver() { if (this.initialized) { return this.configuration.getDecoupledTemplateLogicResolver(); } return this.decoupledTemplateLogicResolver; } /** * <p> * Sets the Decoupled Template Logic Resolver (implementation of * {@link IDecoupledTemplateLogicResolver}) to be used for templates that require so. * </p> * <p> * By default, an instance of {@link org.thymeleaf.templateparser.markup.decoupled.StandardDecoupledTemplateLogicResolver} * 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 decoupledTemplateLogicResolver the engine context factory to be used. * */ public void setDecoupledTemplateLogicResolver(final IDecoupledTemplateLogicResolver decoupledTemplateLogicResolver) { Validate.notNull(decoupledTemplateLogicResolver, "Decoupled Template Logic Resolver cannot be set to null"); checkNotInitialized(); this.decoupledTemplateLogicResolver = decoupledTemplateLogicResolver; } /** * <p> * Returns the set of Message Resolvers configured for this Template Engine. * </p> * * @return the set of message resolvers. */ public final Set<IMessageResolver> getMessageResolvers() { if (this.initialized) { return this.configuration.getMessageResolvers(); } return Collections.unmodifiableSet(this.messageResolvers); } /** * <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<IMessageResolver> messageResolvers) { Validate.notNull(messageResolvers, "Message Resolver set cannot be null"); checkNotInitialized(); this.messageResolvers.clear(); for (final IMessageResolver messageResolver : messageResolvers) { addMessageResolver(messageResolver); } } /** * <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) { Validate.notNull(messageResolver, "Message Resolver cannot be null"); checkNotInitialized(); this.messageResolvers.add(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) { Validate.notNull(messageResolver, "Message Resolver cannot be null"); checkNotInitialized(); this.messageResolvers.clear(); this.messageResolvers.add(messageResolver); } /** * <p> * Returns the set of Link Builders configured for this Template Engine. * </p> * * @return the set of link builders. */ public final Set<ILinkBuilder> getLinkBuilders() { if (this.initialized) { return this.configuration.getLinkBuilders(); } return Collections.unmodifiableSet(this.linkBuilders); } /** * <p> * Sets the link builders 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 linkBuilders the Set of link builders. */ public void setLinkBuilders(final Set<ILinkBuilder> linkBuilders) { Validate.notNull(linkBuilders, "Link Builder set cannot be null"); checkNotInitialized(); this.linkBuilders.clear(); for (final ILinkBuilder linkBuilder : linkBuilders) { addLinkBuilder(linkBuilder); } } /** * <p> * Adds a link builder to the set of link builders 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 linkBuilder the new link builder to be added. */ public void addLinkBuilder(final ILinkBuilder linkBuilder) { Validate.notNull(linkBuilder, "Link Builder cannot be null"); checkNotInitialized(); this.linkBuilders.add(linkBuilder); } /** * <p> * Sets a single link builder for this template engine. * </p> * <p> * Calling this method is equivalent to calling {@link #setLinkBuilders(Set)} * passing a Set with only one link builder. * </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 linkBuilder the message resolver to be set. */ public void setLinkBuilder(final ILinkBuilder linkBuilder) { Validate.notNull(linkBuilder, "Link Builder cannot be null"); checkNotInitialized(); this.linkBuilders.clear(); this.linkBuilders.add(linkBuilder); } /** * <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 (!this.initialized) { initialize(); } this.configuration.getTemplateManager().clearCaches(); } /** * <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 (!this.initialized) { initialize(); } this.configuration.getTemplateManager().clearCachesFor(templateName); } /** * <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(); } public final String process(final String template, final IContext context) { return process(new TemplateSpec(template, null, null, null,null), context); } public final String process(final String template, final Set<String> templateSelectors, final IContext context) { return process(new TemplateSpec(template, templateSelectors, null, null,null), context); } public final String process(final TemplateSpec templateSpec, final IContext context) { final Writer stringWriter = new FastStringWriter(100); process(templateSpec, context, stringWriter); return stringWriter.toString(); } public final void process(final String template, final IContext context, final Writer writer) { process(new TemplateSpec(template, null, null, null,null), context, writer); } public final void process(final String template, final Set<String> templateSelectors, final IContext context, final Writer writer) { process(new TemplateSpec(template, templateSelectors, null, null,null), context, writer); } public final void process(final TemplateSpec templateSpec, final IContext context, final Writer writer) { if (!this.initialized) { initialize(); } try { Validate.notNull(templateSpec, "Template Specification cannot be null"); Validate.notNull(context, "Context cannot be null"); Validate.notNull(writer, "Writer cannot be null"); // selectors CAN actually be null if we are going to render the entire template // templateMode CAN also be null if we are going to use the mode specified by the template resolver if (logger.isTraceEnabled()) { logger.trace("[THYMELEAF][{}] STARTING PROCESS OF TEMPLATE \"{}\" WITH LOCALE {}", new Object[]{TemplateEngine.threadIndex(), templateSpec, context.getLocale()}); } final long startNanos = System.nanoTime(); final TemplateManager templateManager = this.configuration.getTemplateManager(); templateManager.parseAndProcess(templateSpec, context, writer); final long endNanos = System.nanoTime(); if (logger.isTraceEnabled()) { logger.trace("[THYMELEAF][{}] FINISHED PROCESS AND OUTPUT OF TEMPLATE \"{}\" WITH LOCALE {}", new Object[]{TemplateEngine.threadIndex(), templateSpec, context.getLocale()}); } if (timerLogger.isTraceEnabled()) { final BigDecimal elapsed = BigDecimal.valueOf(endNanos - startNanos); final BigDecimal elapsedMs = elapsed.divide(BigDecimal.valueOf(NANOS_IN_SECOND), RoundingMode.HALF_UP); timerLogger.trace( "[THYMELEAF][{}][{}][{}][{}][{}] TEMPLATE \"{}\" WITH LOCALE {} PROCESSED IN {} nanoseconds (approx. {}ms)", new Object[]{ TemplateEngine.threadIndex(), LoggingUtils.loggifyTemplateName(templateSpec.getTemplate()), context.getLocale(), elapsed, elapsedMs, templateSpec, context.getLocale(), elapsed, elapsedMs}); } /* * Finally, flush the writer in order to make sure that everything has been written to output */ try { writer.flush(); } catch (final IOException e) { throw new TemplateOutputException("An error happened while flushing output writer", templateSpec.getTemplate(), -1, -1, e); } } catch (final TemplateOutputException e) { // We log the exception just in case higher levels do not end up logging it (e.g. they could simply display traces in the browser logger.error(String.format("[THYMELEAF][%s] Exception processing template \"%s\": %s", new Object[] {TemplateEngine.threadIndex(), templateSpec, e.getMessage()}), e); throw e; } catch (final TemplateEngineException e) { // We log the exception just in case higher levels do not end up logging it (e.g. they could simply display traces in the browser logger.error(String.format("[THYMELEAF][%s] Exception processing template \"%s\": %s", new Object[] {TemplateEngine.threadIndex(), templateSpec, e.getMessage()}), e); throw e; } catch (final RuntimeException e) { // We log the exception just in case higher levels do not end up logging it (e.g. they could simply display traces in the browser logger.error(String.format("[THYMELEAF][%s] Exception processing template \"%s\": %s", new Object[] {TemplateEngine.threadIndex(), templateSpec, e.getMessage()}), e); throw new TemplateProcessingException("Exception processing template", templateSpec.toString(), e); } } public final IThrottledTemplateProcessor processThrottled(final String template, final IContext context) { return processThrottled(new TemplateSpec(template, null, null, null, null), context); } public final IThrottledTemplateProcessor processThrottled(final String template, final Set<String> templateSelectors, final IContext context) { return processThrottled(new TemplateSpec(template, templateSelectors, null, null, null), context); } public final IThrottledTemplateProcessor processThrottled(final TemplateSpec templateSpec, final IContext context) { if (!this.initialized) { initialize(); } final IThrottledTemplateProcessor throttledTemplateProcessor; try { Validate.notNull(templateSpec, "Template Specification cannot be null"); Validate.notNull(context, "Context cannot be null"); // selectors CAN actually be null if we are going to render the entire template // templateMode CAN also be null if we are going to use the mode specified by the template resolver if (logger.isTraceEnabled()) { logger.trace("[THYMELEAF][{}] STARTING PREPARATION OF THROTTLED TEMPLATE \"{}\" WITH LOCALE {}", new Object[]{TemplateEngine.threadIndex(), templateSpec, context.getLocale()}); } final long startNanos = System.nanoTime(); final TemplateManager templateManager = this.configuration.getTemplateManager(); throttledTemplateProcessor = templateManager.parseAndProcessThrottled(templateSpec, context); final long endNanos = System.nanoTime(); if (logger.isTraceEnabled()) { logger.trace("[THYMELEAF][{}] FINISHED PREPARATION OF THROTTLED TEMPLATE \"{}\" WITH LOCALE {}", new Object[]{TemplateEngine.threadIndex(), templateSpec, context.getLocale()}); } if (timerLogger.isTraceEnabled()) { final BigDecimal elapsed = BigDecimal.valueOf(endNanos - startNanos); final BigDecimal elapsedMs = elapsed.divide(BigDecimal.valueOf(NANOS_IN_SECOND), RoundingMode.HALF_UP); timerLogger.trace( "[THYMELEAF][{}][{}][{}][{}][{}] TEMPLATE \"{}\" WITH LOCALE {} PREPARED FOR THROTTLED PROCESSING IN {} nanoseconds (approx. {}ms)", new Object[]{ TemplateEngine.threadIndex(), LoggingUtils.loggifyTemplateName(templateSpec.getTemplate()), context.getLocale(), elapsed, elapsedMs, templateSpec, context.getLocale(), elapsed, elapsedMs}); } } catch (final TemplateOutputException e) { // We log the exception just in case higher levels do not end up logging it (e.g. they could simply display traces in the browser logger.error(String.format("[THYMELEAF][%s] Exception preparing throttled template \"%s\": %s", new Object[] {TemplateEngine.threadIndex(), templateSpec, e.getMessage()}), e); throw e; } catch (final TemplateEngineException e) { // We log the exception just in case higher levels do not end up logging it (e.g. they could simply display traces in the browser logger.error(String.format("[THYMELEAF][%s] Exception preparing throttled template \"%s\": %s", new Object[] {TemplateEngine.threadIndex(), templateSpec, e.getMessage()}), e); throw e; } catch (final RuntimeException e) { // We log the exception just in case higher levels do not end up logging it (e.g. they could simply display traces in the browser logger.error(String.format("[THYMELEAF][%s] Exception preparing throttled template \"%s\": %s", new Object[] {TemplateEngine.threadIndex(), templateSpec, e.getMessage()}), e); throw new TemplateProcessingException("Exception preparing throttled template", templateSpec.toString(), e); } return throttledTemplateProcessor; } }