This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.scripting.thymeleaf-1.0.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-thymeleaf.git
commit 2f5a44ca9f4bc7d50dcba5b82316392b72bcdd72 Author: Oliver Lietz <[email protected]> AuthorDate: Mon Dec 7 15:54:18 2015 +0000 SLING-5075 Upgrade Thymeleaf to 3.0 * add (missing) patched Thymeleaf sources git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/scripting/thymeleaf@1718412 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/thymeleaf/engine/TemplateManager.java | 776 +++++++++++++++++++++ .../templateresolver/AbstractTemplateResolver.java | 371 ++++++++++ .../templateresolver/ITemplateResolver.java | 156 +++++ 3 files changed, 1303 insertions(+) diff --git a/src/main/java/org/thymeleaf/engine/TemplateManager.java b/src/main/java/org/thymeleaf/engine/TemplateManager.java new file mode 100644 index 0000000..c9eb560 --- /dev/null +++ b/src/main/java/org/thymeleaf/engine/TemplateManager.java @@ -0,0 +1,776 @@ +/* + * ============================================================================= + * + * Copyright (c) 2011-2014, The THYMELEAF team (http://www.thymeleaf.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ============================================================================= + */ +package org.thymeleaf.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.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(); + final String standardDialectPrefix = this.configuration.getStandardDialectPrefix(); + + // 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, standardDialectPrefix); + this.javascriptParser = new JavaScriptTemplateParser(DEFAULT_PARSER_POOL_SIZE, DEFAULT_PARSER_BLOCK_SIZE, standardDialectPresent, standardDialectPrefix); + this.cssParser = new CSSTemplateParser(DEFAULT_PARSER_POOL_SIZE, DEFAULT_PARSER_BLOCK_SIZE, standardDialectPresent, standardDialectPrefix); + 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) { + + 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) { + return cached; + } + } + + + /* + * Resolve the template + */ + final TemplateResolution templateResolution = + resolveTemplate(this.configuration, context, ownerTemplate, template, templateResolutionAttributes); + + + /* + * Build the TemplateData object + */ + final TemplateData templateData = + buildTemplateData(templateResolution, template, cleanTemplateSelectors, templateMode, useCache); + + + /* + * Build the TemplateModel that we will end up returning + */ + final TemplateModel templateModel = new TemplateModel(this.configuration, templateData); + + + /* + * Create the Template Handler that will be in charge of building the TemplateModel + */ + final ModelBuilderTemplateHandler builderHandler = new ModelBuilderTemplateHandler(templateModel.getInternalModel()); + + + /* + * PROCESS THE TEMPLATE + */ + final ITemplateParser parser = getParserForTemplateMode(templateData.getTemplateMode()); + parser.parseStandalone( + this.configuration, + ownerTemplate, template, cleanTemplateSelectors, templateData.getTemplateResource(), + templateData.getTemplateMode(), builderHandler); + + + /* + * Cache the template if it is cacheable + */ + if (useCache && this.templateCache != null) { + if (templateResolution.getValidity().isCacheable()) { + this.templateCache.put(cacheKey, templateModel); + } + } + + return templateModel; + + + + } + + + + + 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)); + + + /* + * Build 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 TemplateModel parsedTemplate = new TemplateModel(this.configuration, templateData); + + + /* + * Create the Template Handler that will be in charge of building the TemplateModel + */ + final ModelBuilderTemplateHandler builderHandler = new ModelBuilderTemplateHandler(parsedTemplate.getInternalModel()); + + + /* + * 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); + + + /* + * 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 + */ + final ITemplateHandler processingHandlerChain = createTemplateProcessingHandlerChain(engineContext, writer); + + /* + * Process the template + */ + template.getInternalModel().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); + + final ITemplateHandler processingHandlerChain = createTemplateProcessingHandlerChain(engineContext, writer); + + cached.getInternalModel().process(processingHandlerChain); + + EngineContextManager.disposeEngineContext(engineContext); + + return; + + } + + } + + + /* + * Resolve the template + */ + final TemplateResolution templateResolution = + resolveTemplate(this.configuration, context, null, template, templateResolutionAttributes); + + + /* + * 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 + */ + final ITemplateHandler processingHandlerChain = createTemplateProcessingHandlerChain(engineContext, 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) { + + // Build the TemplateModel + final TemplateModel templateModel = new TemplateModel(this.configuration, templateData); + + // Create the handler chain to create the Template object + final ModelBuilderTemplateHandler builderHandler = new ModelBuilderTemplateHandler(templateModel.getInternalModel()); + + // Process the cached template itself + parser.parseStandalone( + this.configuration, + null, template, templateSelectors, templateData.getTemplateResource(), + engineContext.getTemplateMode(), builderHandler); + + // Put the new template into cache + this.templateCache.put(cacheKey, templateModel); + + // Process the read (+cached) template itself + templateModel.getInternalModel().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(), processingHandlerChain); + + } + + + /* + * Dispose the engine context now that processing has been done + */ + EngineContextManager.disposeEngineContext(engineContext); + + + } + + + + + + + private static TemplateResolution resolveTemplate( + final IEngineConfiguration configuration, + final IContext context, + final String ownerTemplate, + final String template, + final Map<String, Object> templateResolutionAttributes) { + + // 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)}); + } + + } + + 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 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) + */ + final Set<IPreProcessor> preProcessors = configuration.getPreProcessors(context.getTemplateMode()); + if (preProcessors != null) { + 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 + */ + final ProcessorTemplateHandler processorHandler = new ProcessorTemplateHandler(); + processorHandler.setContext(context); + if (firstHandler == null) { + firstHandler = processorHandler; + lastHandler = processorHandler; + } else { + lastHandler.setNext(processorHandler); + lastHandler = processorHandler; + } + + + /* + * After the Processor Handler, we now must add the post-processors (if any) + */ + final Set<IPostProcessor> postProcessors = configuration.getPostProcessors(context.getTemplateMode()); + if (postProcessors != null) { + 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 + */ + final OutputTemplateHandler outputHandler = new OutputTemplateHandler(writer); + outputHandler.setContext(context); + if (firstHandler == null) { + firstHandler = outputHandler; + } else { + lastHandler.setNext(outputHandler); + } + + return firstHandler; + + } + + + + +} diff --git a/src/main/java/org/thymeleaf/templateresolver/AbstractTemplateResolver.java b/src/main/java/org/thymeleaf/templateresolver/AbstractTemplateResolver.java new file mode 100755 index 0000000..f4805e6 --- /dev/null +++ b/src/main/java/org/thymeleaf/templateresolver/AbstractTemplateResolver.java @@ -0,0 +1,371 @@ +/* + * ============================================================================= + * + * Copyright (c) 2011-2014, The THYMELEAF team (http://www.thymeleaf.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ============================================================================= + */ +package org.thymeleaf.templateresolver; + +import java.util.Map; +import java.util.Set; + +import org.thymeleaf.IEngineConfiguration; +import org.thymeleaf.cache.ICacheEntryValidity; +import org.thymeleaf.context.IContext; +import org.thymeleaf.templatemode.TemplateMode; +import org.thymeleaf.templateresource.ITemplateResource; +import org.thymeleaf.util.PatternSpec; +import org.thymeleaf.util.Validate; + + +/** + * <p> + * Convenience base class for all Template Resolvers. + * </p> + * <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 abstract class AbstractTemplateResolver implements ITemplateResolver { + + /** + * <p> + * By default, resources will not be checked their existence before being returned. This tries to + * avoid a possible performance impact from performing a double access to the resource (one for checking + * existence, another one for reading it). + * </p> + */ + public static final boolean DEFAULT_EXISTENCE_CHECK = false; + + + private String name = this.getClass().getName(); + private Integer order = null; + private boolean checkExistence = DEFAULT_EXISTENCE_CHECK; + + private final PatternSpec resolvablePatternSpec = new PatternSpec(); + + + + + protected AbstractTemplateResolver() { + super(); + } + + + + /** + * <p> + * Returns the name of the template resolver + * </p> + * + * @return the name of the template resolver + */ + public final String getName() { + return this.name; + } + + + /** + * <p> + * Sets a new name for the Template Resolver. + * </p> + * + * @param name the new name + */ + public final void setName(final String name) { + this.name = name; + } + + + /** + * <p> + * Returns the order in which this template resolver will be asked to resolve + * templates as a part of the chain of resolvers configured into the template engine. + * </p> + * <p> + * Order should start with 1. + * </p> + * + * @return the order in which this template resolver will be called in the chain. + */ + public final Integer getOrder() { + return this.order; + } + + + /** + * <p> + * Sets a new order for the template engine in the chain. Order should start with 1. + * </p> + * + * @param order the new order. + */ + public final void setOrder(final Integer order) { + this.order = order; + } + + + + + /** + * <p> + * Returns the <i>pattern spec</i> specified for establishing which + * templates can be resolved by this template resolver. For those templates + * which names do not match this patterns, the Template Resolver will return null. + * </p> + * <p> + * This allows for a fast discard of those templates that the developer might + * know for sure that will not be resolvable by the Resource Resolver used by + * this Template Resolver, so that an execution of the resource resolver is not needed. + * </p> + * + * @return the pattern spec + */ + public final PatternSpec getResolvablePatternSpec() { + return this.resolvablePatternSpec; + } + + /** + * <p> + * Returns the <i>patterns</i> (as String) specified for establishing which + * templates can be resolved by this template resolver. For those templates + * which names do not match this patterns, the Template Resolver will return null. + * </p> + * <p> + * This allows for a fast discard of those templates that the developer might + * know for sure that will not be resolvable by the Resource Resolver used by + * this Template Resolver, so that an execution of the resource resolver is not needed. + * </p> + * <p> + * This is a convenience method equivalent to {@link #getResolvablePatternSpec()}.getPatterns() + * </p> + * + * @return the pattern spec + */ + public final Set<String> getResolvablePatterns() { + return this.resolvablePatternSpec.getPatterns(); + } + + /** + * <p> + * Sets the new <i>patterns</i> to be applied for establishing which + * templates can be resolved by this template resolver. For those templates + * which names do not match this patterns, the Template Resolver will return null. + * </p> + * <p> + * This allows for a fast discard of those templates that the developer might + * know for sure that will not be resolvable by the Resource Resolver used by + * this Template Resolver, so that an execution of the resource resolver is not needed. + * </p> + * <p> + * This is a convenience method equivalent to {@link #getResolvablePatternSpec()}.setPatterns(Set<String>) + * </p> + * + * @param resolvablePatterns the new patterns + */ + public final void setResolvablePatterns(final Set<String> resolvablePatterns) { + this.resolvablePatternSpec.setPatterns(resolvablePatterns); + } + + + + /** + * <p> + * Returns whether template resources will be checked for existence before being returned or not. + * </p> + * <p> + * Default value is <tt>FALSE</tt>. + * </p> + * <p> + * Checking resources for existence will make the template resolver execute {@link ITemplateResource#exists()} + * for each resolved resource before returning a {@link TemplateResolution}, returning <tt>null</tt> if the + * resource does not exist. + * </p> + * <p> + * This allows resolvers to pass control to the next {@link ITemplateResolver} in + * the chain based on real resource existence and not only on the matching performed by the <em>resolvable + * patterns</em> specified at {@link #getResolvablePatterns()}. But at the same time, might pose a performance + * issue on certain scenarios (e.g. HTTP URL resolution) that require actually accessing the resource in order + * to determine its existence, being the resource accessed twice in those cases (once for determining its + * existence, another time for reading it). + * </p> + * <p> + * If this <em>existence check</em> is enabled and a resource is determined to not exist, + * {@link ITemplateResolver#resolveTemplate(IEngineConfiguration, String, String, Map)} will return <tt>null</tt>. + * </p> + * + * @return <tt>true</tt> if resource existence will be checked, <tt>false</tt> if not + * + * @since 3.0.0 + * + */ + public final boolean getCheckExistence() { + return this.checkExistence; + } + + + /** + * <p> + * Sets whether template resources will be checked for existence before being returned or not. + * </p> + * <p> + * Default value is <tt>FALSE</tt>. + * </p> + * <p> + * Checking resources for existence will make the template resolver execute {@link ITemplateResource#exists()} + * for each resolved resource before returning a {@link TemplateResolution}, returning <tt>null</tt> if the + * resource does not exist. + * </p> + * <p> + * This allows resolvers to pass control to the next {@link ITemplateResolver} in + * the chain based on real resource existence and not only on the matching performed by the <em>resolvable + * patterns</em> specified at {@link #getResolvablePatterns()}. But at the same time, might pose a performance + * issue on certain scenarios (e.g. HTTP URL resolution) that require actually accessing the resource in order + * to determine its existence, being the resource accessed twice in those cases (once for determining its + * existence, another time for reading it). + * </p> + * <p> + * If this <em>existence check</em> is enabled and a resource is determined to not exist, + * {@link ITemplateResolver#resolveTemplate(IEngineConfiguration, String, String, Map)} will return <tt>null</tt>. + * </p> + * + * @param checkExistence <tt>true</tt> if resource existence should be checked, <tt>false</tt> if not + * + * @since 3.0.0 + * + */ + public final void setCheckExistence(final boolean checkExistence) { + this.checkExistence = checkExistence; + } + + + public final TemplateResolution resolveTemplate( + final IEngineConfiguration configuration, + final IContext context, + final String ownerTemplate, final String template, + final Map<String, Object> templateResolutionAttributes) { + + Validate.notNull(configuration, "Engine Configuration cannot be null"); + // ownerTemplate CAN be null + Validate.notNull(template, "Template Name cannot be null"); + // templateResolutionAttributes CAN be null + + if (!computeResolvable(configuration, ownerTemplate, template, templateResolutionAttributes)) { + return null; + } + + final ITemplateResource templateResource = computeTemplateResource(configuration, ownerTemplate, template, templateResolutionAttributes); + if (templateResource == null) { + return null; + } + + if (this.checkExistence && !templateResource.exists()) { // will only check if flag set to true + return null; + } + + return new TemplateResolution( + templateResource, + computeTemplateMode(configuration, ownerTemplate, template, templateResolutionAttributes), + computeValidity(configuration, ownerTemplate, template, templateResolutionAttributes)); + + } + + + + + /** + * <p> + * Computes whether a template can be resolved by this resolver or not, + * applying the corresponding patterns. Meant only for use or override by subclasses. + * </p> + * + * @param configuration the engine configuration. + * @param ownerTemplate the owner template, if the resource being computed is a fragment. Might be null. + * @param template the template to be resolved (usually its name). + * @param templateResolutionAttributes the template resolution attributes, if any. Might be null. + * + * @return whether the template is resolvable or not. + */ + protected boolean computeResolvable(final IEngineConfiguration configuration, final String ownerTemplate, final String template, final Map<String, Object> templateResolutionAttributes) { + if (this.resolvablePatternSpec.isEmpty()) { + return true; + } + return this.resolvablePatternSpec.matches(template); + } + + + + + + + /** + * <p> + * Computes the resolved template resource. + * </p> + * + * @param configuration the engine configuration. + * @param ownerTemplate the owner template, if the resource being computed is a fragment. Might be null. + * @param template the template to be resolved (usually its name). + * @param templateResolutionAttributes the template resolution attributes, if any. Might be null. + * + * @return the template resource, or null if this template cannot be resolved (or the resource does not exist). + */ + protected abstract ITemplateResource computeTemplateResource(final IEngineConfiguration configuration, final String ownerTemplate, final String template, final Map<String, Object> templateResolutionAttributes); + + + + /** + * <p> + * Computes the template mode that should be applied to a template, according + * to existing configuration. + * </p> + * + * @param configuration the engine configuration. + * @param ownerTemplate the owner template, if the resource being computed is a fragment. Might be null. + * @param template the template to be resolved (usually its name). + * @param templateResolutionAttributes the template resolution attributes, if any. Might be null. + * + * @return the template mode proposed by the template resolver for the resolved template. + */ + protected abstract TemplateMode computeTemplateMode(final IEngineConfiguration configuration, final String ownerTemplate, final String template, final Map<String, Object> templateResolutionAttributes); + + + + /** + * <p> + * Computes the validity to be applied to the template resolution. This + * includes determining whether the template can be cached or not, and + * also in what circumstances (for instance, for how much time) can + * its cache entry be considered valid. + * </p> + * + * @param configuration the engine configuration. + * @param ownerTemplate the owner template, if the resource being computed is a fragment. Might be null. + * @param template the template to be resolved (usually its name). + * @param templateResolutionAttributes the template resolution attributes, if any. Might be null. + * @return the validity + */ + protected abstract ICacheEntryValidity computeValidity(final IEngineConfiguration configuration, final String ownerTemplate, final String template, final Map<String, Object> templateResolutionAttributes); + + + +} diff --git a/src/main/java/org/thymeleaf/templateresolver/ITemplateResolver.java b/src/main/java/org/thymeleaf/templateresolver/ITemplateResolver.java new file mode 100755 index 0000000..935b97f --- /dev/null +++ b/src/main/java/org/thymeleaf/templateresolver/ITemplateResolver.java @@ -0,0 +1,156 @@ +/* + * ============================================================================= + * + * Copyright (c) 2011-2014, The THYMELEAF team (http://www.thymeleaf.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ============================================================================= + */ +package org.thymeleaf.templateresolver; + +import java.util.Map; + +import org.thymeleaf.IEngineConfiguration; +import org.thymeleaf.context.IContext; +import org.thymeleaf.templatemode.TemplateMode; +import org.thymeleaf.templateresource.ITemplateResource; + +/** + * <p> + * Interface for all Template Resolvers. + * </p> + * <p> + * Template resolvers are in charge of resolving templates into + * {@link TemplateResolution} objects that contain additional information related to + * the template like: + * </p> + * <ul> + * <li>Its corresponding <i>template resource</i> (see + * {@link org.thymeleaf.templateresource.ITemplateResource}).</li> + * <li>The Template Mode to be applied to this template: {@link TemplateMode}</li> + * <li>Whether the template can be cached or not.</li> + * <li>If the template can be cached, (optionally) the time it will live in cache.</li> + * </ul> + * <p> + * The Template Resolver will usually get all this information from a set of configurations + * like applicability patterns, template mode patterns, etc. Each {@link ITemplateResolver} + * implementation will provide its own set of methods for specifying such configurations. + * </p> + * <p> + * The fact that a Template Resolver returns a {@link TemplateResolution} does not necessarily + * mean that the resolved template resource exists. It might only be so if the template resolver + * is configured to perform an <em>existence check</em> on the resource before returning a resolution + * result (by means of calling {@link ITemplateResource#exists()}), which might be configurable on + * a per-{@link ITemplateResolver}-implementation basis. Implementations might choose not to check + * resource existance by default in order to avoid the possible performance impact of a double access + * to the resource. + * </p> + * <p> + * A Template Engine can be configured several template resolvers, and these will + * be asked in order (according to the value returned by {@link #getOrder()}) to return + * a {@link TemplateResolution} object for each template name. If a template resolver + * returns null for a specific resolution, the next one in the chain is asked. Template Resolvers + * that are not configured an order will be executed last in the chain. + * </p> + * <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 + * + * @see ITemplateResource + * @see ClassLoaderTemplateResolver + * @see FileTemplateResolver + * @see ServletContextTemplateResolver + * @see StringTemplateResolver + * @see UrlTemplateResolver + * + * @since 3.0.0 + * + */ +public interface ITemplateResolver { + + + /** + * <p> + * Returns the name of this template resolver. Used in logs and configuration + * details. + * </p> + * + * @return the template resolver name. + */ + public String getName(); + + + /** + * <p> + * Return the order in which this template resolver will be executed in the + * chain when several template resolvers are set for the same Template Engine. + * </p> + * + * @return the order of this resolver in the chain. + */ + public Integer getOrder(); + + + /** + * <p> + * Tries to resolve a template. + * </p> + * <p> + * The method arguments contain all the info needed for trying to + * resolve the template. The Template Resolver will apply its configuration + * (prefixes/suffixes, template mode patterns, cache configurations, etc) and + * return a {@link TemplateResolution} object. + * </p> + * <p> + * The <tt>ownerTemplate</tt>, which might be null, will be specified when the template + * is resolved in order to be used as a fragent to be inserted into a higher level + * template (the <em>owner</em>). Most template resolver implementations will simply ignore + * this argument, but others might change their resolution results depending on the + * owner template that is inserting the resolved fragment. + * </p> + * <p> + * The fact that a Template Resolver returns a {@link TemplateResolution} does not necessarily + * mean that the resolved template resource exists. It might only be so if the template resolver + * is configured to perform an <em>existence check</em> on the resource before returning a resolution + * result (by means of calling {@link ITemplateResource#exists()}), which might be configurable on + * a per-{@link ITemplateResolver}-implementation basis. Implementations might choose not to check + * resource existance by default in order to avoid the possible performance impact of a double access + * to the resource. + * </p> + * <p> + * Note that the <em>template selectors</em> that might be used for a executing or inserting a template + * are not specified to the template resolver. The reason is template selectors are applied by the parser, + * not the template resolvers, and allowing the resolver to take any decisions based on template selectors + * (like e.g. omitting some output from the resource) could harm the correctness of the selection operation + * performed by the parser. + * </p> + * + * @param configuration the engine configuration. + * @param ownerTemplate the containing template from which we want to resolve a new one as a fragment. Can be null. + * @param template the template to be resolved (usually its name). + * @param templateResolutionAttributes the template resolution attributes to be used (usually coming from a + * {@link org.thymeleaf.TemplateSpec} instance. Can be null. + * @return a TemplateResolution object (which might represent an existing resource or not), or null if the + * template could not be resolved. + */ + public TemplateResolution resolveTemplate( + final IEngineConfiguration configuration, + final IContext context, + final String ownerTemplate, final String template, + final Map<String, Object> templateResolutionAttributes); + +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
