/*
* =============================================================================
*
* Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* =============================================================================
*/
package org.thymeleaf;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.thymeleaf.cache.ICacheManager;
import org.thymeleaf.context.IEngineContextFactory;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.engine.AttributeDefinitions;
import org.thymeleaf.engine.ElementDefinitions;
import org.thymeleaf.engine.StandardModelFactory;
import org.thymeleaf.engine.TemplateManager;
import org.thymeleaf.expression.IExpressionObjectFactory;
import org.thymeleaf.linkbuilder.ILinkBuilder;
import org.thymeleaf.messageresolver.IMessageResolver;
import org.thymeleaf.model.IModelFactory;
import org.thymeleaf.postprocessor.IPostProcessor;
import org.thymeleaf.preprocessor.IPreProcessor;
import org.thymeleaf.processor.cdatasection.ICDATASectionProcessor;
import org.thymeleaf.processor.comment.ICommentProcessor;
import org.thymeleaf.processor.doctype.IDocTypeProcessor;
import org.thymeleaf.processor.element.IElementProcessor;
import org.thymeleaf.processor.processinginstruction.IProcessingInstructionProcessor;
import org.thymeleaf.processor.templateboundaries.ITemplateBoundariesProcessor;
import org.thymeleaf.processor.text.ITextProcessor;
import org.thymeleaf.processor.xmldeclaration.IXMLDeclarationProcessor;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateparser.markup.decoupled.IDecoupledTemplateLogicResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.util.Validate;
/**
* <p>
* Default implementation of the {@link IEngineConfiguration} interface.
* </p>
* <p>
* There is normally no reason why user code would directly use this class instead of its interface.
* </p>
*
* @author Daniel Fernández
* @since 3.0.0
*
*/
public class EngineConfiguration implements IEngineConfiguration {
private final DialectSetConfiguration dialectSetConfiguration;
private final Set<ITemplateResolver> templateResolvers;
private final Set<IMessageResolver> messageResolvers;
private final Set<ILinkBuilder> linkBuilders;
private final ICacheManager cacheManager;
private final IEngineContextFactory engineContextFactory;
private final IDecoupledTemplateLogicResolver decoupledTemplateLogicResolver;
private TemplateManager templateManager;
private final ConcurrentHashMap<TemplateMode,IModelFactory> modelFactories;
/*
* There is no reason at all why anyone would want to manually create an instance of this.
*/
EngineConfiguration(
final Set<ITemplateResolver> templateResolvers,
final Set<IMessageResolver> messageResolvers,
final Set<ILinkBuilder> linkBuilders,
final Set<DialectConfiguration> dialectConfigurations,
final ICacheManager cacheManager,
final IEngineContextFactory engineContextFactory,
final IDecoupledTemplateLogicResolver decoupledTemplateLogicResolver) {
super();
Validate.notNull(templateResolvers, "Template Resolver set cannot be null");
Validate.isTrue(templateResolvers.size() > 0, "Template Resolver set cannot be empty");
Validate.containsNoNulls(templateResolvers, "Template Resolver set cannot contain any nulls");
Validate.notNull(messageResolvers, "Message Resolver set cannot be null");
Validate.notNull(dialectConfigurations, "Dialect configuration set cannot be null");
// Cache Manager CAN be null
Validate.notNull(engineContextFactory, "Engine Context Factory cannot be null");
Validate.notNull(decoupledTemplateLogicResolver, "Decoupled Template Logic Resolver cannot be null");
final List<ITemplateResolver> templateResolversList = new ArrayList<ITemplateResolver>(templateResolvers);
Collections.sort(templateResolversList, TemplateResolverComparator.INSTANCE);
this.templateResolvers = Collections.unmodifiableSet(new LinkedHashSet<ITemplateResolver>(templateResolversList));
final List<IMessageResolver> messageResolversList = new ArrayList<IMessageResolver>(messageResolvers);
Collections.sort(messageResolversList, MessageResolverComparator.INSTANCE);
this.messageResolvers = Collections.unmodifiableSet(new LinkedHashSet<IMessageResolver>(messageResolversList));
final List<ILinkBuilder> linkBuilderList = new ArrayList<ILinkBuilder>(linkBuilders);
Collections.sort(linkBuilderList, LinkBuilderComparator.INSTANCE);
this.linkBuilders = Collections.unmodifiableSet(new LinkedHashSet<ILinkBuilder>(linkBuilderList));
this.cacheManager = cacheManager;
this.engineContextFactory = engineContextFactory;
this.decoupledTemplateLogicResolver = decoupledTemplateLogicResolver;
this.dialectSetConfiguration = DialectSetConfiguration.build(dialectConfigurations);
// NOTE we are NOT initializing the templateManager here, but in #initialize()
this.modelFactories = new ConcurrentHashMap<TemplateMode, IModelFactory>(6,1.0f, 1);
}
/*
* We need this method basically in order to initialize variables that have a dependency on the engine configuration
* object itself, and therefore should not be instanced at the constructor.
*/
void initialize() {
this.templateManager = new TemplateManager(this);
}
public Set<ITemplateResolver> getTemplateResolvers() {
return this.templateResolvers;
}
public Set<IMessageResolver> getMessageResolvers() {
return this.messageResolvers;
}
public Set<ILinkBuilder> getLinkBuilders() {
return this.linkBuilders;
}
public ICacheManager getCacheManager() {
return this.cacheManager;
}
public IEngineContextFactory getEngineContextFactory() {
return this.engineContextFactory;
}
public IDecoupledTemplateLogicResolver getDecoupledTemplateLogicResolver() {
return this.decoupledTemplateLogicResolver;
}
public Set<DialectConfiguration> getDialectConfigurations() {
return this.dialectSetConfiguration.getDialectConfigurations();
}
public Set<IDialect> getDialects() {
return this.dialectSetConfiguration.getDialects();
}
public boolean isStandardDialectPresent() {
return this.dialectSetConfiguration.isStandardDialectPresent();
}
public String getStandardDialectPrefix() {
return this.dialectSetConfiguration.getStandardDialectPrefix();
}
public ElementDefinitions getElementDefinitions() {
return this.dialectSetConfiguration.getElementDefinitions();
}
public AttributeDefinitions getAttributeDefinitions() {
return this.dialectSetConfiguration.getAttributeDefinitions();
}
public Set<ITemplateBoundariesProcessor> getTemplateBoundariesProcessors(final TemplateMode templateMode) {
return this.dialectSetConfiguration.getTemplateBoundariesProcessors(templateMode);
}
public Set<ICDATASectionProcessor> getCDATASectionProcessors(final TemplateMode templateMode) {
return this.dialectSetConfiguration.getCDATASectionProcessors(templateMode);
}
public Set<ICommentProcessor> getCommentProcessors(final TemplateMode templateMode) {
return this.dialectSetConfiguration.getCommentProcessors(templateMode);
}
public Set<IDocTypeProcessor> getDocTypeProcessors(final TemplateMode templateMode) {
return this.dialectSetConfiguration.getDocTypeProcessors(templateMode);
}
public Set<IElementProcessor> getElementProcessors(final TemplateMode templateMode) {
return this.dialectSetConfiguration.getElementProcessors(templateMode);
}
public Set<ITextProcessor> getTextProcessors(final TemplateMode templateMode) {
return this.dialectSetConfiguration.getTextProcessors(templateMode);
}
public Set<IProcessingInstructionProcessor> getProcessingInstructionProcessors(final TemplateMode templateMode) {
return this.dialectSetConfiguration.getProcessingInstructionProcessors(templateMode);
}
public Set<IXMLDeclarationProcessor> getXMLDeclarationProcessors(final TemplateMode templateMode) {
return this.dialectSetConfiguration.getXMLDeclarationProcessors(templateMode);
}
public Set<IPreProcessor> getPreProcessors(final TemplateMode templateMode) {
return this.dialectSetConfiguration.getPreProcessors(templateMode);
}
public Set<IPostProcessor> getPostProcessors(final TemplateMode templateMode) {
return this.dialectSetConfiguration.getPostProcessors(templateMode);
}
public Map<String, Object> getExecutionAttributes() {
return this.dialectSetConfiguration.getExecutionAttributes();
}
public IExpressionObjectFactory getExpressionObjectFactory() {
return this.dialectSetConfiguration.getExpressionObjectFactory();
}
public TemplateManager getTemplateManager() {
return this.templateManager;
}
public IModelFactory getModelFactory(final TemplateMode templateMode) {
if (this.modelFactories.containsKey(templateMode)) {
return this.modelFactories.get(templateMode);
}
// TODO The classes to be instanced for creating model factories should be configurable
final IModelFactory modelFactory = new StandardModelFactory(this, templateMode);
this.modelFactories.putIfAbsent(templateMode, modelFactory);
return this.modelFactories.get(templateMode);
}
/*
* This method is NOT a part of the IEngineConfiguration interface. It is only meant for internal usage. It's
* aim is to determine if the template engine has been configured any processing structures that might be harmed
* if the model parsed for a template is reshaped in order to improve efficiency during processing (e.g. turn
* inlined texts into th:block's or condense non-processable template events into Texts)
*/
public boolean isModelReshapeable(final TemplateMode templateMode) {
if (!this.dialectSetConfiguration.isStandardDialectPresent()) {
// If we haven't configured the standard dialect, preprocessing inlined expressions makes no sense and
// in any case we would be in risk of not knowing what we are doing...
return false;
}
final Set<ITextProcessor> textProcessors = this.dialectSetConfiguration.getTextProcessors(templateMode);
// If the Standard Dialect is present, the inlining text processor will be there. If there are
// more than that, then it is not the standard dialect the only one wanting to process texts, which means
// we should not disturb the text event structure by applying inlining preprocessing here.
if (textProcessors.size() > 1) {
return false;
}
if (templateMode.isMarkup()) {
final Set<ICommentProcessor> commentProcessors = this.dialectSetConfiguration.getCommentProcessors(templateMode);
// If the Standard Dialect is present, the inlining text processor will be there. But there will be an additional
// comment processor: the conditional comments one. So everytime we reshape a comment, not only we must check it
// is not inlineable, but also that it is not a conditional comment
if (commentProcessors.size() > (templateMode == TemplateMode.HTML ? 2 : 1)) {
return false;
}
final Set<ICDATASectionProcessor> cdataSectionProcessors = this.dialectSetConfiguration.getCDATASectionProcessors(templateMode);
// If the Standard Dialect is present, the inlining text processor will be there.
if (cdataSectionProcessors.size() > 1) {
return false;
}
}
// Last, we need no preprocessors and also no post-processors present. Any reshaping of the model would
// affect them in unexpected ways, so better not do anything if they exist
if (!this.dialectSetConfiguration.getPreProcessors(templateMode).isEmpty()) {
return false;
}
return this.dialectSetConfiguration.getPostProcessors(templateMode).isEmpty();
}
/**
* Compares <tt>Integer</tt> types, taking into account possible <tt>null</tt>
* values. When <tt>null</tt>, then the return value will be such that the
* other value will come first in a comparison. If both values are <tt>null</tt>,
* then they are effectively equal.
*
* @param o1 The first value to compare.
* @param o2 The second value to compare.
* @return -1, 0, or 1 if the first value should come before, equal to, or
* after the second.
*/
private static int nullSafeIntegerComparison(Integer o1, Integer o2) {
return o1 != null ? o2 != null ? o1.compareTo(o2) : -1 : o2 != null ? 1 : 0;
}
private static final class TemplateResolverComparator implements Comparator<ITemplateResolver> {
private static TemplateResolverComparator INSTANCE = new TemplateResolverComparator();
TemplateResolverComparator() {
super();
}
public int compare(final ITemplateResolver tr1, final ITemplateResolver tr2) {
return nullSafeIntegerComparison(tr1.getOrder(), tr2.getOrder());
}
}
private static final class MessageResolverComparator implements Comparator<IMessageResolver> {
private static MessageResolverComparator INSTANCE = new MessageResolverComparator();
MessageResolverComparator() {
super();
}
public int compare(final IMessageResolver mr1, final IMessageResolver mr2) {
return nullSafeIntegerComparison(mr1.getOrder(), mr2.getOrder());
}
}
private static final class LinkBuilderComparator implements Comparator<ILinkBuilder> {
private static LinkBuilderComparator INSTANCE = new LinkBuilderComparator();
LinkBuilderComparator() {
super();
}
public int compare(final ILinkBuilder mr1, final ILinkBuilder mr2) {
return nullSafeIntegerComparison(mr1.getOrder(), mr2.getOrder());
}
}
}