org.thymeleaf.engine.TemplateManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of thymeleaf Show documentation
Show all versions of thymeleaf Show documentation
Modern server-side Java template engine for both web and standalone environments
/*
* =============================================================================
*
* Copyright (c) 2011-2018, 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.engine;
import java.io.Writer;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.TemplateSpec;
import org.thymeleaf.cache.AlwaysValidCacheEntryValidity;
import org.thymeleaf.cache.ICache;
import org.thymeleaf.cache.ICacheEntryValidity;
import org.thymeleaf.cache.ICacheManager;
import org.thymeleaf.cache.NonCacheableCacheEntryValidity;
import org.thymeleaf.cache.TemplateCacheKey;
import org.thymeleaf.context.IContext;
import org.thymeleaf.context.IEngineContext;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.exceptions.TemplateInputException;
import org.thymeleaf.exceptions.TemplateProcessingException;
import org.thymeleaf.postprocessor.IPostProcessor;
import org.thymeleaf.preprocessor.IPreProcessor;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateparser.ITemplateParser;
import org.thymeleaf.templateparser.markup.HTMLTemplateParser;
import org.thymeleaf.templateparser.markup.XMLTemplateParser;
import org.thymeleaf.templateparser.raw.RawTemplateParser;
import org.thymeleaf.templateparser.text.CSSTemplateParser;
import org.thymeleaf.templateparser.text.JavaScriptTemplateParser;
import org.thymeleaf.templateparser.text.TextTemplateParser;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolution;
import org.thymeleaf.templateresource.ITemplateResource;
import org.thymeleaf.util.LoggingUtils;
import org.thymeleaf.util.Validate;
/**
*
* @author Daniel Fernández
*
* @since 3.0.0
*
*/
public final class TemplateManager {
private static final Logger logger = LoggerFactory.getLogger(TemplateManager.class);
private static final int DEFAULT_PARSER_POOL_SIZE = 40;
private static final int DEFAULT_PARSER_BLOCK_SIZE = 2048;
private final IEngineConfiguration configuration;
private final ITemplateParser htmlParser;
private final ITemplateParser xmlParser;
private final ITemplateParser textParser;
private final ITemplateParser javascriptParser;
private final ITemplateParser cssParser;
private final ITemplateParser rawParser;
private final ICache templateCache; // might be null! (= no cache)
/**
*
* This constructor should only be called directly for testing purposes.
*
*
* @param configuration the engine configuration
*/
public TemplateManager(final IEngineConfiguration configuration) {
super();
Validate.notNull(configuration, "Configuration cannot be null");
this.configuration = configuration;
final ICacheManager cacheManager = this.configuration.getCacheManager();
if (cacheManager == null) {
this.templateCache = null;
} else {
this.templateCache = cacheManager.getTemplateCache();
}
final boolean standardDialectPresent = this.configuration.isStandardDialectPresent();
// TODO Make these parser implementations configurable: one parser per template mode, then make default implementations extensible/configurable (e.g. AttoParser config)
this.htmlParser = new HTMLTemplateParser(DEFAULT_PARSER_POOL_SIZE,DEFAULT_PARSER_BLOCK_SIZE);
this.xmlParser = new XMLTemplateParser(DEFAULT_PARSER_POOL_SIZE, DEFAULT_PARSER_BLOCK_SIZE);
this.textParser = new TextTemplateParser(DEFAULT_PARSER_POOL_SIZE, DEFAULT_PARSER_BLOCK_SIZE, standardDialectPresent);
this.javascriptParser = new JavaScriptTemplateParser(DEFAULT_PARSER_POOL_SIZE, DEFAULT_PARSER_BLOCK_SIZE, standardDialectPresent);
this.cssParser = new CSSTemplateParser(DEFAULT_PARSER_POOL_SIZE, DEFAULT_PARSER_BLOCK_SIZE, standardDialectPresent);
this.rawParser = new RawTemplateParser(DEFAULT_PARSER_POOL_SIZE, DEFAULT_PARSER_BLOCK_SIZE);
}
/**
*
* Clears the template cache.
*
*/
public void clearCaches() {
if (this.templateCache != null) {
this.templateCache.clear();
}
}
/**
*
* Clears any existing entries for template of the specified
* name at the template cache.
*
*
* @param template the name of the template whose entries have to be cleared.
*/
public void clearCachesFor(final String template) {
Validate.notNull(template, "Cannot specify null template");
if (this.templateCache != null) {
final Set keysToBeRemoved = new HashSet(4);
final Set templateCacheKeys = this.templateCache.keySet();
// We are iterating twice and creating a temporary set just in case the 'keySet' Set is still connected
// to the original cache store and we provoke ConcurrentModificationExceptions when removing entries
for (final TemplateCacheKey templateCacheKey : templateCacheKeys) {
final String ownerTemplate = templateCacheKey.getOwnerTemplate();
if (ownerTemplate != null) {
// It's not a standalone template, so we are interested on the owner template
if (ownerTemplate.equals(template)) {
keysToBeRemoved.add(templateCacheKey);
}
} else {
if (templateCacheKey.getTemplate().equals(template)) {
keysToBeRemoved.add(templateCacheKey);
}
}
}
for (final TemplateCacheKey keyToBeRemoved : keysToBeRemoved) {
this.templateCache.clearKey(keyToBeRemoved);
}
}
}
/*
* -------------
* PARSE methods
* -------------
*
* Parse methods will create 'template models' that are basically collections of events in the form of an
* immutable IModel implementation.
*/
public TemplateModel parseStandalone(
final ITemplateContext context, final String template, final Set templateSelectors,
final TemplateMode templateMode, final boolean useCache, final boolean failIfNotExists) {
Validate.notNull(context, "Context cannot be null");
Validate.notNull(template, "Template cannot be null");
// templateSelectors CAN be null if we are going to render the entire template
// templateMode CAN be null if we are going to use the mode specified by the template resolver
// templateResolutionAttributes CAN be null
final String ownerTemplate = context.getTemplateData().getTemplate();
final Map templateResolutionAttributes = context.getTemplateResolutionAttributes();
final Set cleanTemplateSelectors;
if (templateSelectors != null && !templateSelectors.isEmpty()) {
Validate.containsNoEmpties(
templateSelectors, "If specified, the Template Selector set cannot contain any nulls or empties");
if (templateSelectors.size() == 1) {
cleanTemplateSelectors = Collections.singleton(templateSelectors.iterator().next());
} else {
// We will be using a TreeSet because we want the selectors to be ORDERED, so that comparison at the
// equals(...) method works alright
cleanTemplateSelectors = Collections.unmodifiableSet(new TreeSet(templateSelectors));
}
} else {
cleanTemplateSelectors = null;
}
final TemplateCacheKey cacheKey =
useCache?
new TemplateCacheKey(
ownerTemplate,
template, cleanTemplateSelectors,
0, 0,
templateMode,
templateResolutionAttributes)
: null;
/*
* First look at the cache - it might be already cached
*/
if (useCache && this.templateCache != null) {
final TemplateModel cached = this.templateCache.get(cacheKey);
if (cached != null) {
/*
* Just at the end, and importantly AFTER CACHING, check if we need to apply any pre-processors
* to this model before returning and letting the engine insert the model in any way it needs.
*/
return applyPreProcessorsIfNeeded(context, cached);
}
}
/*
* Resolve the template
*/
final TemplateResolution templateResolution =
resolveTemplate(this.configuration, ownerTemplate, template, templateResolutionAttributes, failIfNotExists);
/*
* Once the template has been resolved (or tried to), and depending on the value of our 'failIfNotExists'
* flag, we will check two conditions in which we will be returning null:
*
* 1. No template resolver has been able to resolve the template (this can happen if resolvers are
* configured with the 'checkExistence' flag to true).
* 2. If the template was resolved, its existence should be checked in order to avoid exceptions during
* the reading phase.
*
* NOTE we will not cache this "null" result because the fact that a template is cacheable or not is
* determined by template resolvers. And in this case there is no template resolver being applied
* (actually, we are here because no resolver had success).
*/
if (!failIfNotExists) {
if (templateResolution == null) {
// No resolver could resolve this
return null;
}
if (!templateResolution.isTemplateResourceExistenceVerified()) {
final ITemplateResource resource = templateResolution.getTemplateResource();
if (resource == null || !resource.exists()) {
// Calling resource.exists() each time is not great, but think this only happens if the resource
// has not been cached (e.g. when it does not exist)
return null;
}
}
}
/*
* Build the TemplateData object
*/
final TemplateData templateData =
buildTemplateData(templateResolution, template, cleanTemplateSelectors, templateMode, useCache);
/*
* Create the Template Handler that will be in charge of building the TemplateModel
*/
final ModelBuilderTemplateHandler builderHandler = new ModelBuilderTemplateHandler(this.configuration, templateData);
/*
* PROCESS THE TEMPLATE
*/
final ITemplateParser parser = getParserForTemplateMode(templateData.getTemplateMode());
parser.parseStandalone(
this.configuration,
ownerTemplate, template, cleanTemplateSelectors, templateData.getTemplateResource(),
templateData.getTemplateMode(), templateResolution.getUseDecoupledLogic(), builderHandler);
final TemplateModel templateModel = builderHandler.getModel();
/*
* Cache the template if it is cacheable
*/
if (useCache && this.templateCache != null) {
if (templateResolution.getValidity().isCacheable()) {
this.templateCache.put(cacheKey, templateModel);
}
}
/*
* Last step: just at the end, and importantly AFTER CACHING, check if we need to apply any pre-processors
* to this model before returning and letting the engine insert the model in any way it needs.
*/
return applyPreProcessorsIfNeeded(context, templateModel);
}
/*
* This method manually applies preprocessors to template models that have just been parsed or obtained from
* cache. This is needed for fragments, just before these fragments (coming from templates, not simply parsed
* text) are returned to whoever needs them (usually the fragment insertion mechanism).
*
* NOTE that PRE-PROCESSOR INSTANCES ARE NOT SHARED among the different fragments being inserted
* in a template (or between fragments and the main template). The reason for this is that pre-processors are
* implementations of ITemplateHandler and therefore instances are inserted into processing chains that cannot
* be broken (if a pre-processor is used for the main template its "next" step in the chain cannot be
* 'momentarily' changed in order to be a fragment-building handler instead of the ProcessorTemplateHandler)
*
* The only way therefore among pre-processor instances to actually share information is by setting it into
* the context.
*/
private TemplateModel applyPreProcessorsIfNeeded(final ITemplateContext context, final TemplateModel templateModel) {
final TemplateData templateData = templateModel.getTemplateData();
if (this.configuration.getPreProcessors(templateData.getTemplateMode()).isEmpty()) {
return templateModel;
}
final IEngineContext engineContext =
EngineContextManager.prepareEngineContext(this.configuration, templateData, context.getTemplateResolutionAttributes(), context);
final ModelBuilderTemplateHandler builderHandler = new ModelBuilderTemplateHandler(this.configuration, templateData);
final ITemplateHandler processingHandlerChain =
createTemplateProcessingHandlerChain(engineContext, true, false, builderHandler, null);
templateModel.process(processingHandlerChain);
EngineContextManager.disposeEngineContext(engineContext);
return builderHandler.getModel();
}
public TemplateModel parseString(
final TemplateData ownerTemplateData, final String template,
final int lineOffset, final int colOffset,
final TemplateMode templateMode,
final boolean useCache) {
Validate.notNull(ownerTemplateData, "Owner template cannot be null");
Validate.notNull(template, "Template cannot be null");
// NOTE selectors cannot be specified when parsing a nested template
// templateMode CAN be null (if we are using the owner's)
final String ownerTemplate = ownerTemplateData.getTemplate();
final TemplateMode definitiveTemplateMode =
(templateMode != null? templateMode : ownerTemplateData.getTemplateMode());
final TemplateCacheKey cacheKey =
useCache?
new TemplateCacheKey(
ownerTemplate,
template, null,
lineOffset, colOffset,
definitiveTemplateMode,
null) // template resolution attributes do not affect string fragments: no resolution!
: null;
/*
* First look at the cache - it might be already cached
*/
if (useCache && this.templateCache != null) {
final TemplateModel cached = this.templateCache.get(cacheKey);
if (cached != null) {
return cached;
}
}
/*
* Compute the cache validity. In order for a String fragment to be cacheable, we will have to have
* specified the 'useCache' parameter as true, and the owner template must be cacheable
*/
final ICacheEntryValidity cacheValidity =
(useCache && ownerTemplateData.getValidity().isCacheable()?
AlwaysValidCacheEntryValidity.INSTANCE : NonCacheableCacheEntryValidity.INSTANCE);
/*
* Build the TemplateData
*
* NOTE how, by default, we are using the owner's TemplateData. And even if the template mode changes
* and we need to create a new TemplateData object, we will keep the original name and resource.
* This is because we want the elements inside the fragment to me reported as belonging to the
* container template, not to the fragment String considered as a fragment in its own (which
* wouldn't make sense)
*/
final TemplateData templateData =
(templateMode == null?
// No change in Template Mode -> simply use the owner's template data
ownerTemplateData :
// Template Mode changed -> new TemplateData, very similar but different template mode
new TemplateData(
ownerTemplateData.getTemplate(), ownerTemplateData.getTemplateSelectors(),
ownerTemplateData.getTemplateResource(), templateMode, cacheValidity));
/*
* Create the Template Handler that will be in charge of building the TemplateModel
*
* NOTE how we are using the owner's TemplateData and not a new one created for this fragment, because
* we want the elements inside the fragment to me reported as belonging to the container template,
* not to the fragment String considered as a fragment in its own (which wouldn't make sense)
*/
final ModelBuilderTemplateHandler builderHandler = new ModelBuilderTemplateHandler(this.configuration, templateData);
/*
* PROCESS THE TEMPLATE
*/
final ITemplateParser parser = getParserForTemplateMode(templateData.getTemplateMode());
// NO RESOURCE is sent to the parser, in this case. We simply pass the String template
parser.parseString(this.configuration, ownerTemplate, template, lineOffset, colOffset, definitiveTemplateMode, builderHandler);
final TemplateModel parsedTemplate = builderHandler.getModel();
/*
* Cache the template if it is cacheable
*/
if (useCache && this.templateCache != null) {
if (cacheValidity.isCacheable()) {
this.templateCache.put(cacheKey, parsedTemplate);
}
}
return parsedTemplate;
}
/*
* ---------------
* PROCESS methods
* ---------------
*
* Processing means executing a template that has already been parsed into a TemplateModel object
*/
public void process(
final TemplateModel template,
final ITemplateContext context,
final Writer writer) {
Validate.isTrue(
this.configuration == template.getConfiguration(),
"Specified template was built by a different Template Engine instance");
/*
* Create the context instance that corresponds to this execution of the template engine
*/
final IEngineContext engineContext =
EngineContextManager.prepareEngineContext(this.configuration, template.getTemplateData(), context.getTemplateResolutionAttributes(), context);
/*
* Create the handler chain to process the data.
*
* In this case we are only processing an already existing model, which was created after some computation
* at template-processing time. So this does not come directly from a template, and therefore pre-processors
* should not be applied.
*
* As for post-processors, we know the result of this will not be directly written to output in most cases but
* instead used to create a String that is afterwards inserted into the model as a Text node. In the only cases
* in which this is not true is when this is used inside any kind of Lazy-processing CharSequence writer like
* LazyProcessingCharSequence, and in such case we know those CharSequences are only used when there are
* NO post-processors, so we are safe anyway.
*/
final ProcessorTemplateHandler processorTemplateHandler = new ProcessorTemplateHandler();
final ITemplateHandler processingHandlerChain =
createTemplateProcessingHandlerChain(engineContext, false, false, processorTemplateHandler, writer);
/*
* Process the template
*/
template.process(processingHandlerChain);
/*
* Dispose the engine context now that processing has been done
*/
EngineContextManager.disposeEngineContext(engineContext);
}
/*
* -------------------------
* PARSE-AND-PROCESS methods
* -------------------------
*
* These methods perform the whole cycle of a template's processing: resolving, parsing and processing.
* This is only meant to be called from the TemplateEngine
*/
public void parseAndProcess(
final TemplateSpec templateSpec,
final IContext context,
final Writer writer) {
Validate.notNull(templateSpec, "Template Specification cannot be null");
Validate.notNull(context, "Context cannot be null");
Validate.notNull(writer, "Writer cannot be null");
// TemplateSpec will already have validated its contents, so need to do it here (template selectors,
// resolution attributes, etc.)
final String template = templateSpec.getTemplate();
final Set templateSelectors = templateSpec.getTemplateSelectors();
final TemplateMode templateMode = templateSpec.getTemplateMode();
final Map templateResolutionAttributes = templateSpec.getTemplateResolutionAttributes();
final TemplateCacheKey cacheKey =
new TemplateCacheKey(
null, // ownerTemplate
template, templateSelectors,
0, 0, // lineOffset, colOffset
templateMode,
templateResolutionAttributes);
/*
* First look at the cache - it might be already cached
*/
if (this.templateCache != null) {
final TemplateModel cached = this.templateCache.get(cacheKey);
if (cached != null) {
final IEngineContext engineContext =
EngineContextManager.prepareEngineContext(this.configuration, cached.getTemplateData(), templateResolutionAttributes, context);
/*
* Create the handler chain to process the data.
* This is PARSE + PROCESS, so its called from the TemplateEngine, and the only case in which we should apply
* both pre-processors and post-processors (besides creating a last output-to-writer step)
*/
final ProcessorTemplateHandler processorTemplateHandler = new ProcessorTemplateHandler();
final ITemplateHandler processingHandlerChain =
createTemplateProcessingHandlerChain(engineContext, true, true, processorTemplateHandler, writer);
cached.process(processingHandlerChain);
EngineContextManager.disposeEngineContext(engineContext);
return;
}
}
/*
* Resolve the template
*/
final TemplateResolution templateResolution =
resolveTemplate(this.configuration, null, template, templateResolutionAttributes, true);
/*
* Build the TemplateData object
*/
final TemplateData templateData =
buildTemplateData(templateResolution, template, templateSelectors, templateMode, true);
/*
* Prepare the context instance that corresponds to this execution of the template engine
*/
final IEngineContext engineContext =
EngineContextManager.prepareEngineContext(this.configuration, templateData, templateResolutionAttributes, context);
/*
* Create the handler chain to process the data.
* This is PARSE + PROCESS, so its called from the TemplateEngine, and the only case in which we should apply
* both pre-processors and post-processors (besides creating a last output-to-writer step)
*/
final ProcessorTemplateHandler processorTemplateHandler = new ProcessorTemplateHandler();
final ITemplateHandler processingHandlerChain =
createTemplateProcessingHandlerChain(engineContext, true, true, processorTemplateHandler, writer);
/*
* Obtain the parser
*/
final ITemplateParser parser = getParserForTemplateMode(engineContext.getTemplateMode());
/*
* If the resolved template is cacheable, so we will first read it as an object, cache it, and then process it
*/
if (templateResolution.getValidity().isCacheable() && this.templateCache != null) {
// Create the handler chain to create the Template object
final ModelBuilderTemplateHandler builderHandler = new ModelBuilderTemplateHandler(this.configuration, templateData);
// Process the template into a TemplateModel
parser.parseStandalone(
this.configuration,
null, template, templateSelectors, templateData.getTemplateResource(),
engineContext.getTemplateMode(), templateResolution.getUseDecoupledLogic(), builderHandler);
// Obtain the TemplateModel
final TemplateModel templateModel = builderHandler.getModel();
// Put the new template into cache
this.templateCache.put(cacheKey, templateModel);
// Process the read (+cached) template itself
templateModel.process(processingHandlerChain);
} else {
// Process the template, which is not cacheable (so no worry about caching)
parser.parseStandalone(
this.configuration,
null, template, templateSelectors, templateData.getTemplateResource(),
engineContext.getTemplateMode(), templateResolution.getUseDecoupledLogic(), processingHandlerChain);
}
/*
* Dispose the engine context now that processing has been done
*/
EngineContextManager.disposeEngineContext(engineContext);
}
public ThrottledTemplateProcessor parseAndProcessThrottled(final TemplateSpec templateSpec, final IContext context) {
Validate.notNull(templateSpec, "Template Specification cannot be null");
Validate.notNull(context, "Context cannot be null");
// TemplateSpec will already have validated its contents, so need to do it here (template selectors,
// resolution attributes, etc.)
final String template = templateSpec.getTemplate();
final Set templateSelectors = templateSpec.getTemplateSelectors();
final TemplateMode templateMode = templateSpec.getTemplateMode();
final Map templateResolutionAttributes = templateSpec.getTemplateResolutionAttributes();
final TemplateCacheKey cacheKey =
new TemplateCacheKey(
null, // ownerTemplate
template, templateSelectors,
0, 0, // lineOffset, colOffset
templateMode,
templateResolutionAttributes);
/*
* Instantiate the throttling artifacts, including the throttled writer, which might be only for
*/
final TemplateFlowController flowController = new TemplateFlowController();
final ThrottledTemplateWriter throttledTemplateWriter;
if (templateSpec.isOutputSSE()) {
throttledTemplateWriter = new SSEThrottledTemplateWriter(template, flowController);
} else {
throttledTemplateWriter = new ThrottledTemplateWriter(template, flowController);
}
/*
* First look at the cache - it might be already cached
*/
if (this.templateCache != null) {
final TemplateModel cached = this.templateCache.get(cacheKey);
if (cached != null) {
final IEngineContext engineContext =
EngineContextManager.prepareEngineContext(this.configuration, cached.getTemplateData(), templateResolutionAttributes, context);
/*
* Create the handler chain to process the data.
* This is PARSE + PROCESS, so its called from the TemplateEngine, and the only case in which we should apply
* both pre-processors and post-processors (besides creating a last output-to-writer step)
*/
final ProcessorTemplateHandler processorTemplateHandler = new ProcessorTemplateHandler();
processorTemplateHandler.setFlowController(flowController);
final ITemplateHandler processingHandlerChain =
createTemplateProcessingHandlerChain(engineContext, true, true, processorTemplateHandler, throttledTemplateWriter);
/*
* Return the throttled template processor
*/
return new ThrottledTemplateProcessor(
templateSpec, engineContext, cached, processingHandlerChain,
processorTemplateHandler, flowController, throttledTemplateWriter);
}
}
/*
* Resolve the template
*/
final TemplateResolution templateResolution =
resolveTemplate(this.configuration, null, template, templateResolutionAttributes, true);
/*
* Build the TemplateData object
*/
final TemplateData templateData =
buildTemplateData(templateResolution, template, templateSelectors, templateMode, true);
/*
* Prepare the context instance that corresponds to this execution of the template engine
*/
final IEngineContext engineContext =
EngineContextManager.prepareEngineContext(this.configuration, templateData, templateResolutionAttributes, context);
/*
* Create the handler chain to process the data.
* This is PARSE + PROCESS, so its called from the TemplateEngine, and the only case in which we should apply
* both pre-processors and post-processors (besides creating a last output-to-writer step)
*/
final ProcessorTemplateHandler processorTemplateHandler = new ProcessorTemplateHandler();
processorTemplateHandler.setFlowController(flowController);
final ITemplateHandler processingHandlerChain =
createTemplateProcessingHandlerChain(engineContext, true, true, processorTemplateHandler, throttledTemplateWriter);
/*
* Obtain the parser
*/
final ITemplateParser parser = getParserForTemplateMode(engineContext.getTemplateMode());
/*
* Parse the template into a TemplateModel. Even if we are not using the cache, throttled template processings
* will always be processed first into a TemplateModel, so that throttling can then be applied on an
* already-in-memory sequence of events
*/
final ModelBuilderTemplateHandler builderHandler = new ModelBuilderTemplateHandler(this.configuration, templateData);
parser.parseStandalone(
this.configuration,
null, template, templateSelectors, templateData.getTemplateResource(),
engineContext.getTemplateMode(), templateResolution.getUseDecoupledLogic(), builderHandler);
final TemplateModel templateModel = builderHandler.getModel();
/*
* If cache is active, put the cached TemplateModel into cache
*/
if (templateResolution.getValidity().isCacheable() && this.templateCache != null) {
// Put the new template into cache
this.templateCache.put(cacheKey, templateModel);
}
/*
* Return the throttled template processor
*/
return new ThrottledTemplateProcessor(
templateSpec, engineContext, templateModel, processingHandlerChain,
processorTemplateHandler, flowController, throttledTemplateWriter);
}
private static TemplateResolution resolveTemplate(
final IEngineConfiguration configuration,
final String ownerTemplate,
final String template,
final Map templateResolutionAttributes,
final boolean failIfNotExists) {
// Note that the MARKUP SELECTORS that might be used for a executing or inserting a template
// are not specified to the template resolver. The reason is markup selectors are applied by the parser,
// not the template resolvers, and allowing the resolver to take any decisions based on markup selectors
// (like e.g. omitting some output from the resource) could harm the correctness of the selection operation
// performed by the parser.
for (final ITemplateResolver templateResolver : configuration.getTemplateResolvers()) {
final TemplateResolution templateResolution =
templateResolver.resolveTemplate(configuration, ownerTemplate, template, templateResolutionAttributes);
if (templateResolution != null) {
if (logger.isTraceEnabled()) {
logger.trace(
"[THYMELEAF][{}] Template resolver match! Resolver \"{}\" will resolve template \"{}\"",
new Object[] {TemplateEngine.threadIndex(), templateResolver.getName(), LoggingUtils.loggifyTemplateName(template)});
}
return templateResolution;
}
if (logger.isTraceEnabled()) {
logger.trace(
"[THYMELEAF][{}] Skipping template resolver \"{}\" for template \"{}\"",
new Object[] {TemplateEngine.threadIndex(), templateResolver.getName(), LoggingUtils.loggifyTemplateName(template)});
}
}
if (!failIfNotExists) {
// In this case we will not consider that a "not exists" means a failure. Maybe we are in a scenario
// (e.g. some types of operations with FragmentExpressions) in which we desire this.
return null;
}
throw new TemplateInputException(
"Error resolving template [" + template + "], " +
"template might not exist or might not be accessible by " +
"any of the configured Template Resolvers");
}
private static TemplateData buildTemplateData(
final TemplateResolution templateResolution,
final String template,
final Set templateSelectors,
final TemplateMode templateMode,
final boolean useCache) {
final TemplateMode definitiveTemplateMode =
(templateMode == null ? templateResolution.getTemplateMode() : templateMode);
final ICacheEntryValidity definitiveCacheEntryValidity =
(useCache? templateResolution.getValidity() : NonCacheableCacheEntryValidity.INSTANCE);
return new TemplateData(
template, templateSelectors, templateResolution.getTemplateResource(), definitiveTemplateMode, definitiveCacheEntryValidity);
}
private ITemplateParser getParserForTemplateMode(final TemplateMode templateMode) {
switch (templateMode) {
case HTML: return this.htmlParser;
case XML: return this.xmlParser;
case TEXT: return this.textParser;
case JAVASCRIPT: return this.javascriptParser;
case CSS: return this.cssParser;
case RAW: return this.rawParser;
default:
throw new IllegalArgumentException("No parser exists for template mode: " + templateMode);
}
}
private static ITemplateHandler createTemplateProcessingHandlerChain(
final IEngineContext context,
final boolean setPreProcessors, final boolean setPostProcessors,
final ITemplateHandler handler, final Writer writer) {
final IEngineConfiguration configuration = context.getConfiguration();
/*
* Declare the pair of pointers that will allow us to build the chain of template handlers
*/
ITemplateHandler firstHandler = null;
ITemplateHandler lastHandler = null;
/*
* First type of handlers to be added: pre-processors (if any)
*/
if (setPreProcessors) {
final Set preProcessors = configuration.getPreProcessors(context.getTemplateMode());
if (preProcessors != null && preProcessors.size() > 0) {
for (final IPreProcessor preProcessor : preProcessors) {
final Class extends ITemplateHandler> preProcessorClass = preProcessor.getHandlerClass();
final ITemplateHandler preProcessorHandler;
try {
preProcessorHandler = preProcessorClass.newInstance();
} catch (final Exception e) {
// This should never happen - class was already checked during configuration to contain a zero-arg constructor
throw new TemplateProcessingException(
"An exception happened during the creation of a new instance of pre-processor " + preProcessorClass.getClass().getName(), e);
}
// Initialize the pre-processor
preProcessorHandler.setContext(context);
if (firstHandler == null) {
firstHandler = preProcessorHandler;
lastHandler = preProcessorHandler;
} else {
lastHandler.setNext(preProcessorHandler);
lastHandler = preProcessorHandler;
}
}
}
}
/*
* Initialize and add to the chain te Processor Handler itself, the central piece of the chain
*/
handler.setContext(context);
if (firstHandler == null) {
firstHandler = handler;
lastHandler = handler;
} else {
lastHandler.setNext(handler);
lastHandler = handler;
}
/*
* After the Processor Handler, we now must add the post-processors (if any)
*/
if (setPostProcessors) {
final Set postProcessors = configuration.getPostProcessors(context.getTemplateMode());
if (postProcessors != null && postProcessors.size() > 0) {
for (final IPostProcessor postProcessor : postProcessors) {
final Class extends ITemplateHandler> postProcessorClass = postProcessor.getHandlerClass();
final ITemplateHandler postProcessorHandler;
try {
postProcessorHandler = postProcessorClass.newInstance();
} catch (final Exception e) {
// This should never happen - class was already checked during configuration to contain a zero-arg constructor
throw new TemplateProcessingException(
"An exception happened during the creation of a new instance of post-processor " + postProcessorClass.getClass().getName(), e);
}
// Initialize the pre-processor
postProcessorHandler.setContext(context);
if (firstHandler == null) {
firstHandler = postProcessorHandler;
lastHandler = postProcessorHandler;
} else {
lastHandler.setNext(postProcessorHandler);
lastHandler = postProcessorHandler;
}
}
}
}
/*
* Last step: the OUTPUT HANDLER
*/
if (writer != null) {
final OutputTemplateHandler outputHandler = new OutputTemplateHandler(writer);
outputHandler.setContext(context);
if (firstHandler == null) {
firstHandler = outputHandler;
} else {
lastHandler.setNext(outputHandler);
}
}
return firstHandler;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy