/* * ============================================================================= * * Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ============================================================================= */ package org.thymeleaf; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.thymeleaf.templatemode.TemplateMode; import org.thymeleaf.util.ContentTypeUtils; import org.thymeleaf.util.LoggingUtils; import org.thymeleaf.util.Validate; /** * <p> * Specification class containing everything needed by the template engine related to the * template to be processed. Objects of this class are normally used as an argument to the * different <tt>process(...)</tt> methods at {@link ITemplateEngine}. * </p> * <p> * The only required value in a template specification is the <em>template</em>, which normally * represents the <em>template name</em>, but can be the entire template contents if the template * is meant to be specified as a String and resolved by a * {@link org.thymeleaf.templateresolver.StringTemplateResolver}. * </p> * <p> * This is not to be mistaken for the template processing <strong>context</strong>, containing * the data to be used during the processing of the template (variables, locale, etc.) and * modelled by the {@link org.thymeleaf.context.IContext} interface. * </p> * <p> * The data contained in a Template Specification relates to and identifies the template itself, * independently of any data (variables, etc.) used for processing it. * </p> * <p> * Objects of this class are <strong>thread-safe</strong>. * </p> * * @author Daniel Fernández * * @since 3.0.0 * */ public final class TemplateSpec implements Serializable { private static final long serialVersionUID = 51214133L; private final String template; private final Set<String> templateSelectors; private final TemplateMode templateMode; private final Map<String,Object> templateResolutionAttributes; private final String outputContentType; private final boolean outputSSE; /** * <p> * Build a new object of this class, specifying <em>template</em> and also <em>template mode</em>. * </p> * <p> * The <em>template</em> normally represents the <em>template name</em>, but can be the entire template * contents if the template is meant to be specified as a String and resolved by a * {@link org.thymeleaf.templateresolver.StringTemplateResolver}. * </p> * <p> * The template mode only needs to be specified in cases when we want to <em>force</em> a template * mode to be used for a template, independently of the mode that is selected for it by the configured * template resolvers. * </p> * <p> * This constructor will set no <em>template selectors</em> or <em>template resolution attributes</em>. * </p> * * @param template the template (usually the template name), required. * @param templateMode the template mode to be forced, can be null. */ public TemplateSpec(final String template, final TemplateMode templateMode) { this(template, null, templateMode, null, null); } /** * <p> * Build a new object of this class, specifying <em>template</em> and also <em>output content type</em> * (MIME type). Most of the times this will force a template mode for template execution * (e.g. <tt>text/html</tt>, <tt>application/javascript</tt>), but not always (e.g. <tt>text/event-stream</tt>). * </p> * <p> * The <em>template</em> normally represents the <em>template name</em>, but can be the entire template * contents if the template is meant to be specified as a String and resolved by a * {@link org.thymeleaf.templateresolver.StringTemplateResolver}. * </p> * <p> * Supported relations between template mode and output content type are: * </p> * <ul> * <li>HTML: <tt>text/html</tt>, <tt>application/xhtml+xml</tt></li> * <li>XML: <tt>application/xml</tt></li> * <li>JAVASCRIPT: <tt>application/javascript</tt>, <tt>application/x-javascript</tt>, * <tt>application/ecmascript</tt>, <tt>text/javascript</tt>, * <tt>text/ecmascript</tt>, <tt>application/json</tt></li> * <li>CSS: <tt>text/css</tt></li> * <li>TEXT: <tt>text/plain</tt></li> * </ul> * <p> * The <tt>text/event-stream</tt> content type will also be supported, but will have no effect in * forcing a template mode. Instead, it will put the engine into Server-Sent Event (SSE) output mode. * </p> * <p> * Note content type parameters will be ignored (only the mime type itself will be used). * </p> * <p> * This constructor will set no <em>template selectors</em> or <em>template resolution attributes</em>. * </p> * * @param template the template (usually the template name), required. * @param outputContentType the expected output content type, can be null. * * @since 3.0.6 */ public TemplateSpec(final String template, final String outputContentType) { this(template, null, null, outputContentType, null); } /** * <p> * Build a new object of this class, specifying <em>template</em> and also <em>template mode</em>. * </p> * <p> * The <em>template</em> normally represents the <em>template name</em>, but can be the entire template * contents if the template is meant to be specified as a String and resolved by a * {@link org.thymeleaf.templateresolver.StringTemplateResolver}. * </p> * <p> * The template resolution attributes are meant to be passed to the template resolvers (see * {@link org.thymeleaf.templateresolver.ITemplateResolver} during template resolution, as a way * of configuring their execution for the template being processed. * </p> * <p> * Note that template resolution attributes are considered a part of the <em>identifier</em> of a template, * so they will be used as a part of the keys for cached templates. <strong>It is therefore * required that template attribute maps contain values with valid {@link #equals(Object)} * and {@link #hashCode()} implementations</strong>. Therefore, using simple (and fast) * <tt>Map<String,String></tt> maps is the recommended option. * </p> * <p> * This constructor will set no <em>template selectors</em> or <em>forced template mode</em>. * </p> * * @param template the template (usually the template name), required. * @param templateResolutionAttributes the template resolution attributes, can be null. */ public TemplateSpec(final String template, final Map<String, Object> templateResolutionAttributes) { this(template, null, null, null, templateResolutionAttributes); } /** * <p> * Build a new object of this class, specifying a template mode that should be forced for template * execution, ignoring the mode resolved by template resolvers. * </p> * <p> * The <em>template</em> usually represents the <em>template name</em>, but can be the entire template * contents if the template is meant to be specified as a String and resolved by a * {@link org.thymeleaf.templateresolver.StringTemplateResolver}. * </p> * <p> * Template selectors allow the possibility to process only a part of the specified template, expressing * this selection in a syntax similar to jQuery, CSS or XPath selectors. Note this is only available for * <em>markup template modes</em> (<tt>HTML</tt>, <tt>XML</tt>). For more info on <em>template selectors</em> * syntax, have a look at <a href="http://www.attoparser.org">AttoParser</a>'s <em>markup selectors</em> * documentation. * </p> * <p> * The template mode only needs to be specified in cases when we want to <em>force</em> a template * mode to be used for a template, independently of the mode that is selected for it by the configured * template resolvers. * </p> * <p> * The template resolution attributes are meant to be passed to the template resolvers (see * {@link org.thymeleaf.templateresolver.ITemplateResolver} during template resolution, as a way * of configuring their execution for the template being processed. * </p> * <p> * Note that template resolution attributes are considered a part of the <em>identifier</em> of a template, * so they will be used as a part of the keys for cached templates. <strong>It is therefore * required that template attribute maps contain values with valid {@link #equals(Object)} * and {@link #hashCode()} implementations</strong>. Therefore, using simple (and fast) * <tt>Map<String,String></tt> maps is the recommended option. * </p> * * @param template the template (usually the template name), required. * @param templateSelectors the template selectors to be applied on the template. * @param templateMode the template mode to be forced, can be null. * @param templateResolutionAttributes the template resolution attributes, can be null. */ public TemplateSpec( final String template, final Set<String> templateSelectors, final TemplateMode templateMode, final Map<String, Object> templateResolutionAttributes) { this(template, templateSelectors, templateMode, null, templateResolutionAttributes); } /** * <p> * Build a new object of this class, specifying an output content type (MIME type). Most of the times this * will force a template mode for template execution (e.g. <tt>text/html</tt>, <tt>application/javascript</tt>), * but not always (e.g. <tt>text/event-stream</tt>). * </p> * <p> * The <em>template</em> usually represents the <em>template name</em>, but can be the entire template * contents if the template is meant to be specified as a String and resolved by a * {@link org.thymeleaf.templateresolver.StringTemplateResolver}. * </p> * <p> * Template selectors allow the possibility to process only a part of the specified template, expressing * this selection in a syntax similar to jQuery, CSS or XPath selectors. Note this is only available for * <em>markup template modes</em> (<tt>HTML</tt>, <tt>XML</tt>). For more info on <em>template selectors</em> * syntax, have a look at <a href="http://www.attoparser.org">AttoParser</a>'s <em>markup selectors</em> * documentation. * </p> * <p> * The template resolution attributes are meant to be passed to the template resolvers (see * {@link org.thymeleaf.templateresolver.ITemplateResolver} during template resolution, as a way * of configuring their execution for the template being processed. * </p> * <p> * Supported relations between template mode and output content type are: * </p> * <ul> * <li>HTML: <tt>text/html</tt>, <tt>application/xhtml+xml</tt></li> * <li>XML: <tt>application/xml</tt></li> * <li>JAVASCRIPT: <tt>application/javascript</tt>, <tt>application/x-javascript</tt>, * <tt>application/ecmascript</tt>, <tt>text/javascript</tt>, * <tt>text/ecmascript</tt>, <tt>application/json</tt></li> * <li>CSS: <tt>text/css</tt></li> * <li>TEXT: <tt>text/plain</tt></li> * </ul> * <p> * The <tt>text/event-stream</tt> content type will also be supported, but will have no effect in * forcing a template mode. Instead, it will put the engine into Server-Sent Event (SSE) output mode. * </p> * <p> * Note content type parameters will be ignored (only the mime type itself will be used). * </p> * <p> * Note that template resolution attributes are considered a part of the <em>identifier</em> of a template, * so they will be used as a part of the keys for cached templates. <strong>It is therefore * required that template attribute maps contain values with valid {@link #equals(Object)} * and {@link #hashCode()} implementations</strong>. Therefore, using simple (and fast) * <tt>Map<String,String></tt> maps is the recommended option. * </p> * * @param template the template (usually the template name), required. * @param templateSelectors the template selectors to be applied on the template. * @param outputContentType the expected output content type, can be null. * @param templateResolutionAttributes the template resolution attributes, can be null. * * @since 3.0.6 */ public TemplateSpec( final String template, final Set<String> templateSelectors, final String outputContentType, final Map<String, Object> templateResolutionAttributes) { this(template, templateSelectors, null, outputContentType, templateResolutionAttributes); } /** * <p> * Build a new object of this class, specifying all its attributes. * </p> * * @param template the template (usually the template name), required. * @param templateSelectors the template selectors to be applied on the template. * @param templateMode the template mode to be forced, can be null. * @param outputContentType the expected output content type, can be null. * @param templateResolutionAttributes the template resolution attributes, can be null. * * @since 3.0.4 */ TemplateSpec( final String template, final Set<String> templateSelectors, final TemplateMode templateMode, final String outputContentType, final Map<String, Object> templateResolutionAttributes) { super(); Validate.notNull(template, "Template cannot be null"); Validate.isTrue(templateMode == null || outputContentType == null, "If template mode or output content type are specified, the other one cannot"); // templateSelectors CAN be null // templateMode CAN be null // outputContentType CAN be null // templateResolutionAttributes CAN be null // ONLY one of templateMode or outputContentType can be not-null this.template = template; if (templateSelectors != null && !templateSelectors.isEmpty()) { Validate.containsNoEmpties( templateSelectors, "If specified, the Template Selector set cannot contain any nulls or empties"); if (templateSelectors.size() == 1) { this.templateSelectors = 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 this.templateSelectors = Collections.unmodifiableSet(new TreeSet<String>(templateSelectors)); } } else { this.templateSelectors = null; } this.templateResolutionAttributes = (templateResolutionAttributes != null && !templateResolutionAttributes.isEmpty()? Collections.unmodifiableMap(new HashMap<String, Object>(templateResolutionAttributes)) : null); this.outputContentType = outputContentType; final TemplateMode computedTemplateMode = ContentTypeUtils.computeTemplateModeForContentType(this.outputContentType); if (computedTemplateMode != null) { this.templateMode = computedTemplateMode; } else { this.templateMode = templateMode; } this.outputSSE = ContentTypeUtils.isContentTypeSSE(this.outputContentType); } /** * <p> * Returns the template (usually the template name). * </p> * <p> * This <em>template</em> normally represents the <em>template name</em>, but can be the entire template * contents if the template is meant to be specified as a String and resolved by a * {@link org.thymeleaf.templateresolver.StringTemplateResolver}. * </p> * * @return the template. Cannot be null. */ public String getTemplate() { return this.template; } /** * <p> * Returns whether this spec has template selectors specified or not. * </p> * * @return <tt>true</tt> of there are template selectors, <tt>false</tt> if not. */ public boolean hasTemplateSelectors() { // Checking for null is enough, as we have already processed this in the constructor return this.templateSelectors != null; } /** * <p> * Returns the template selectors, if there are any. * </p> * <p> * Template selectors allow the possibility to process only a part of the specified template, expressing * this selection in a syntax similar to jQuery, CSS or XPath selectors. Note this is only available for * <em>markup template modes</em> (<tt>HTML</tt>, <tt>XML</tt>). For more info on <em>template selectors</em> * syntax, have a look at <a href="http://www.attoparser.org">AttoParser</a>'s <em>markup selectors</em> * documentation. * </p> * * @return the template selectors, or <tt>null</tt> if there are none. */ public Set<String> getTemplateSelectors() { return this.templateSelectors; } /** * <p> * Returns whether this spec has template mode specified or not. * </p> * * @return <tt>true</tt> of there is a template mode, <tt>false</tt> if not. */ public boolean hasTemplateMode() { return this.templateMode != null; } /** * <p> * Returns the template mode, if it has been specified. * </p> * <p> * The template mode only needs to be specified in cases when we want to <em>force</em> a template * mode to be used for a template, independently of the mode that is selected for it by the configured * template resolvers. * </p> * * @return the template mode specified, or <tt>null</tt> if there isn't any. */ public TemplateMode getTemplateMode() { return this.templateMode; } /** * <p> * Returns whether this spec includes template resolution attributes or not. * </p> * * @return <tt>true</tt> of there are template resolution attributes, <tt>false</tt> if not. */ public boolean hasTemplateResolutionAttributes() { // Checking for null is enough, as we have already processed this in the constructor return this.templateResolutionAttributes != null; } /** * <p> * Returns the template resolution attributes, if any have been specified. * </p> * <p> * The template resolution attributes are meant to be passed to the template resolvers (see * {@link org.thymeleaf.templateresolver.ITemplateResolver} during template resolution, as a way * of configuring their execution for the template being processed. * </p> * <p> * Note that template resolution attributes are considered a part of the <em>identifier</em> of a template, * so they will be used as a part of the keys for cached templates. <strong>It is therefore * required that template attribute maps contain values with valid {@link #equals(Object)} * and {@link #hashCode()} implementations</strong>. Therefore, using simple (and fast) * <tt>Map<String,String></tt> maps is the recommended option. * </p> * * @return the template resolution attributes. */ public Map<String, Object> getTemplateResolutionAttributes() { return this.templateResolutionAttributes; } /** * <p> * Returns the output content type (MIME type). Most of the times this * will force a template mode for template execution (e.g. <tt>text/html</tt>, <tt>application/javascript</tt>), * but not always (e.g. <tt>text/event-stream</tt>). * </p> * <p> * Supported relations between template mode and output content type are: * </p> * <ul> * <li>HTML: <tt>text/html</tt>, <tt>application/xhtml+xml</tt></li> * <li>XML: <tt>application/xml</tt></li> * <li>JAVASCRIPT: <tt>application/javascript</tt>, <tt>application/x-javascript</tt>, * <tt>application/ecmascript</tt>, <tt>text/javascript</tt>, * <tt>text/ecmascript</tt>, <tt>application/json</tt></li> * <li>CSS: <tt>text/css</tt></li> * <li>TEXT: <tt>text/plain</tt></li> * </ul> * <p> * The <tt>text/event-stream</tt> content type will also be supported, but will have no effect in * forcing a template mode. Instead, it will put the engine into Server-Sent Event (SSE) output mode. * </p> * <p> * Note content type parameters will be ignored (only the mime type itself will be used). * </p> * * @return the output content type, or <tt>null</tt> if none was specified. */ public String getOutputContentType() { return this.outputContentType; } /** * <p> * Returns whether output should be Server-Sent Events (SSE) or not. * </p> * <p> * Server-Sent Events mode is enabled by setting the <tt>text/event-stream</tt> mime type * as *output content type* constructor argument. * </p> * * @return true if output is supposed to be done via Server-Sent Events (SSE), false if not. * * @since 3.0.6 */ public boolean isOutputSSE() { return this.outputSSE; } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (!(o instanceof TemplateSpec)) { return false; } final TemplateSpec that = (TemplateSpec) o; if (!this.template.equals(that.template)) { return false; } if (this.templateSelectors != null ? !this.templateSelectors.equals(that.templateSelectors) : that.templateSelectors != null) { return false; } if (this.templateMode != that.templateMode) { return false; } if (!this.outputContentType.equals(that.outputContentType)) { return false; } // Note how it is important that template resolution attribute values correctly implement equals() and hashCode() return !(this.templateResolutionAttributes != null ? !this.templateResolutionAttributes.equals(that.templateResolutionAttributes) : that.templateResolutionAttributes != null); } @Override public int hashCode() { int result = this.template.hashCode(); result = 31 * result + (this.templateSelectors != null ? this.templateSelectors.hashCode() : 0); result = 31 * result + (this.templateMode != null ? this.templateMode.hashCode() : 0); result = 31 * result + (this.outputContentType != null ? this.outputContentType.hashCode() : 0); result = 31 * result + (this.templateResolutionAttributes != null ? this.templateResolutionAttributes.hashCode() : 0); return result; } @Override public String toString() { final StringBuilder strBuilder = new StringBuilder(); strBuilder.append(LoggingUtils.loggifyTemplateName(this.template)); if (this.templateSelectors != null) { strBuilder.append("::"); strBuilder.append(this.templateSelectors); } if (this.templateMode != null) { strBuilder.append(" @"); strBuilder.append(this.templateMode); } if (this.templateResolutionAttributes != null) { strBuilder.append(" ("); strBuilder.append(this.templateResolutionAttributes); strBuilder.append(")"); } if (this.outputContentType != null) { strBuilder.append(" ["); strBuilder.append(this.outputContentType); strBuilder.append("]"); } return strBuilder.toString(); } }