| /* |
| * ============================================================================= |
| * |
| * 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.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<TemplateCacheKey,TemplateModel> templateCache; // might be null! (= no cache) |
| |
| |
| |
| |
| /** |
| * <p> |
| * This constructor should only be called directly for <strong>testing purposes</strong>. |
| * </p> |
| * |
| * @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); |
| |
| } |
| |
| |
| |
| |
| |
| /** |
| * <p> |
| * Clears the template cache. |
| * </p> |
| */ |
| public void clearCaches() { |
| if (this.templateCache != null) { |
| this.templateCache.clear(); |
| } |
| } |
| |
| |
| /** |
| * <p> |
| * Clears any existing entries for template of the specified |
| * name at the template cache. |
| * </p> |
| * |
| * @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<TemplateCacheKey> keysToBeRemoved = new HashSet<TemplateCacheKey>(4); |
| final Set<TemplateCacheKey> 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<String> 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<String,Object> templateResolutionAttributes = context.getTemplateResolutionAttributes(); |
| |
| final Set<String> 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<String>(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, context, 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<String> templateSelectors = templateSpec.getTemplateSelectors(); |
| final TemplateMode templateMode = templateSpec.getTemplateMode(); |
| final Map<String, Object> 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, context, 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<String> templateSelectors = templateSpec.getTemplateSelectors(); |
| final TemplateMode templateMode = templateSpec.getTemplateMode(); |
| final Map<String, Object> 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, context, 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 IContext context, |
| final String ownerTemplate, |
| final String template, |
| final Map<String, Object> 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, context, 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 \"" + LoggingUtils.loggifyTemplateName(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<String> 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<IPreProcessor> 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<IPostProcessor> 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; |
| |
| } |
| |
| |
| |
| |
| } |