You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:12:18 UTC

[sling-org-apache-sling-scripting-thymeleaf] 02/17: SLING-3649 make Thymeleaf scripting configurable and extensible

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.scripting.thymeleaf-0.0.2
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-thymeleaf.git

commit accb73b10b0fb49b2a0175dea07c729d1961a943
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Tue Jun 10 15:07:36 2014 +0000

    SLING-3649 make Thymeleaf scripting configurable and extensible
    
    - out of the box support for legacy HTML5 through embedded NekoHTML
    - runtime configurable TemplateModeHandlers for XML, VALIDXML, XHTML, VALIDXHTML, HTML5 and LEGACYHTML5
    - MessageResolver backed by ResourceBundleProvider from org.apache.sling.i18n
    - uses UTF-8 charset for reading templates (not configurable)
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/scripting/thymeleaf@1601661 13f79535-47bb-0310-9956-ffa450edef68
---
 README.md                                          |  10 +-
 pom.xml                                            |  23 +++-
 .../sling/scripting/thymeleaf/SlingContext.java    |  27 +---
 ...Resolver.java => SlingTemplateModeHandler.java} |  26 +---
 .../scripting/thymeleaf/SlingTemplateResolver.java |  62 ---------
 .../sling/scripting/thymeleaf/SlingWebContext.java | 123 +++++++++++++++++
 .../scripting/thymeleaf/ThymeleafScriptEngine.java |  31 +++--
 .../thymeleaf/ThymeleafScriptEngineFactory.java    | 138 ++++++++++++++++---
 .../thymeleaf/impl/NonCachingTemplateResolver.java | 148 +++++++++++++++++++++
 .../impl/ResourceBundleMessageResolver.java        | 123 +++++++++++++++++
 .../ScriptReaderResourceResolver.java}             |  28 +++-
 .../AbstractTemplateModeHandler.java               | 110 +++++++++++++++
 .../Html5TemplateModeHandler.java                  |  64 +++++++++
 .../LegacyHtml5TemplateModeHandler.java            |  64 +++++++++
 .../ValidatingXhtmlTemplateModeHandler.java        |  64 +++++++++
 .../ValidatingXmlTemplateModeHandler.java          |  64 +++++++++
 .../XhtmlTemplateModeHandler.java                  |  64 +++++++++
 .../XmlTemplateModeHandler.java                    |  64 +++++++++
 .../OSGI-INF/metatype/metatype.properties          |  43 +++++-
 19 files changed, 1125 insertions(+), 151 deletions(-)

diff --git a/README.md b/README.md
index 693fcae..85faa4d 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,15 @@
 Apache Sling Scripting Thymeleaf
 ================================
 
-scripting engine for Thymeleaf templates
+scripting engine for _Thymeleaf_ templates
 
 * http://www.thymeleaf.org
 * https://github.com/thymeleaf/thymeleaf
+
+Features
+--------
+
+* out of the box support for _legacy_ HTML5 through embedded _NekoHTML_
+* runtime configurable `TemplateModeHandler`s for _XML_, _VALIDXML_, _XHTML_, _VALIDXHTML_, _HTML5_ and _LEGACYHTML5_
+* `MessageResolver` backed by `ResourceBundleProvider` from `org.apache.sling.i18n`
+* uses `UTF-8` charset for reading templates
diff --git a/pom.xml b/pom.xml
index 9d0ef77..c14df0a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,7 +38,7 @@
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
-    <sling.java.version>7</sling.java.version>
+    <sling.java.version>6</sling.java.version>
   </properties>
 
   <scm>
@@ -60,6 +60,11 @@
       <artifactId>org.osgi.core</artifactId>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <scope>provided</scope>
+    </dependency>
     <!-- Apache Commons -->
     <dependency>
       <groupId>commons-io</groupId>
@@ -80,6 +85,18 @@
       <version>2.1.6</version>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.commons.osgi</artifactId>
+      <version>2.2.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.i18n</artifactId>
+      <version>2.2.8</version>
+      <scope>provided</scope>
+    </dependency>
     <!-- Apache Felix -->
     <dependency>
       <groupId>org.apache.felix</groupId>
@@ -127,8 +144,8 @@
         <artifactId>maven-compiler-plugin</artifactId>
         <version>3.1</version>
         <configuration>
-          <source>1.7</source>
-          <target>1.7</target>
+          <source>1.6</source>
+          <target>1.6</target>
         </configuration>
       </plugin>
       <plugin>
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/SlingContext.java b/src/main/java/org/apache/sling/scripting/thymeleaf/SlingContext.java
index 733614a..a4fc418 100644
--- a/src/main/java/org/apache/sling/scripting/thymeleaf/SlingContext.java
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/SlingContext.java
@@ -19,32 +19,11 @@
 package org.apache.sling.scripting.thymeleaf;
 
 import java.io.Reader;
-import java.util.Locale;
-import java.util.Map;
 
-import org.thymeleaf.context.Context;
+import org.thymeleaf.context.IContext;
 
-// TODO WebContext?
-public class SlingContext extends Context {
+public interface SlingContext extends IContext {
 
-    private final Reader reader;
-
-    public SlingContext(final Reader reader) {
-        this.reader = reader;
-    }
-
-    public SlingContext(Locale locale, final Reader reader) {
-        super(locale);
-        this.reader = reader;
-    }
-
-    public SlingContext(Locale locale, Map<String, ?> variables, final Reader reader) {
-        super(locale, variables);
-        this.reader = reader;
-    }
-
-    public Reader getReader() {
-        return reader;
-    }
+    Reader getReader();
 
 }
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/SlingMessageResolver.java b/src/main/java/org/apache/sling/scripting/thymeleaf/SlingTemplateModeHandler.java
similarity index 56%
rename from src/main/java/org/apache/sling/scripting/thymeleaf/SlingMessageResolver.java
rename to src/main/java/org/apache/sling/scripting/thymeleaf/SlingTemplateModeHandler.java
index cf811e0..abb35c8 100644
--- a/src/main/java/org/apache/sling/scripting/thymeleaf/SlingMessageResolver.java
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/SlingTemplateModeHandler.java
@@ -18,29 +18,11 @@
  */
 package org.apache.sling.scripting.thymeleaf;
 
-import org.thymeleaf.Arguments;
-import org.thymeleaf.messageresolver.IMessageResolver;
-import org.thymeleaf.messageresolver.MessageResolution;
+import org.thymeleaf.PatternSpec;
+import org.thymeleaf.templatemode.ITemplateModeHandler;
 
-public class SlingMessageResolver implements IMessageResolver {
+public interface SlingTemplateModeHandler extends ITemplateModeHandler {
 
-    @Override
-    public String getName() {
-        return getClass().getSimpleName();
-    }
-
-    @Override
-    public Integer getOrder() {
-        return 0; // TODO make configurable
-    }
-
-    @Override
-    public MessageResolution resolveMessage(final Arguments arguments, final String key, final Object[] messageParameters) {
-        return new MessageResolution("TODO"); // TODO
-    }
-
-    @Override
-    public void initialize() {
-    }
+    PatternSpec getPatternSpec();
 
 }
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/SlingTemplateResolver.java b/src/main/java/org/apache/sling/scripting/thymeleaf/SlingTemplateResolver.java
deleted file mode 100644
index 173a31a..0000000
--- a/src/main/java/org/apache/sling/scripting/thymeleaf/SlingTemplateResolver.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.apache.sling.scripting.thymeleaf;
-
-import java.nio.charset.StandardCharsets;
-
-import org.thymeleaf.TemplateProcessingParameters;
-import org.thymeleaf.templatemode.StandardTemplateModeHandlers;
-import org.thymeleaf.templateresolver.ITemplateResolutionValidity;
-import org.thymeleaf.templateresolver.ITemplateResolver;
-import org.thymeleaf.templateresolver.NonCacheableTemplateResolutionValidity;
-import org.thymeleaf.templateresolver.TemplateResolution;
-
-public class SlingTemplateResolver implements ITemplateResolver {
-
-    final SlingResourceResolver resourceResolver;
-
-    public SlingTemplateResolver(final SlingResourceResolver resourceResolver) {
-        this.resourceResolver = resourceResolver;
-    }
-
-    @Override
-    public String getName() {
-        return getClass().getSimpleName();
-    }
-
-    @Override
-    public Integer getOrder() {
-        return 0; // TODO make configurable
-    }
-
-    @Override
-    public TemplateResolution resolveTemplate(TemplateProcessingParameters templateProcessingParameters) {
-        final String templateName = templateProcessingParameters.getTemplateName();
-        final String resourceName = templateName; // TODO
-        final String characterEncoding = StandardCharsets.UTF_8.name();
-        final String templateMode = StandardTemplateModeHandlers.LEGACYHTML5.getTemplateModeName(); // TODO make configurable
-        final ITemplateResolutionValidity validity = new NonCacheableTemplateResolutionValidity(); // TODO
-        return new TemplateResolution(templateName, resourceName, resourceResolver, characterEncoding, templateMode, validity);
-    }
-
-    @Override
-    public void initialize() {
-    }
-
-}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/SlingWebContext.java b/src/main/java/org/apache/sling/scripting/thymeleaf/SlingWebContext.java
new file mode 100644
index 0000000..afbdfd8
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/SlingWebContext.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.scripting.thymeleaf;
+
+import java.io.Reader;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.thymeleaf.context.AbstractContext;
+import org.thymeleaf.context.IWebContext;
+import org.thymeleaf.context.VariablesMap;
+import org.thymeleaf.context.WebContextExecutionInfo;
+import org.thymeleaf.util.Validate;
+
+public class SlingWebContext implements SlingContext, IWebContext {
+
+    private final Locale locale;
+
+    private final VariablesMap<String, Object> variables = new VariablesMap<String, Object>();
+
+    private final SlingHttpServletRequest servletRequest;
+
+    private final SlingHttpServletResponse servletResponse;
+
+    private final ServletContext servletContext;
+
+    private final Reader reader;
+
+    public SlingWebContext(final SlingHttpServletRequest servletRequest, final SlingHttpServletResponse servletResponse, final ServletContext servletContext, final Locale locale, final Map<String, ?> variables, final Reader reader) {
+        this.servletRequest = servletRequest;
+        this.servletResponse = servletResponse;
+        this.servletContext = servletContext;
+        this.locale = locale;
+        this.variables.putAll(variables);
+        this.reader = reader;
+    }
+
+    @Override
+    public SlingHttpServletRequest getHttpServletRequest() {
+        return servletRequest;
+    }
+
+    @Override
+    public SlingHttpServletResponse getHttpServletResponse() {
+        return servletResponse;
+    }
+
+    @Override
+    public HttpSession getHttpSession() {
+        return servletRequest.getSession(false);
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return servletContext;
+    }
+
+    @Override
+    public VariablesMap<String, String[]> getRequestParameters() {
+        return null;
+    }
+
+    @Override
+    public VariablesMap<String, Object> getRequestAttributes() {
+        return null;
+    }
+
+    @Override
+    public VariablesMap<String, Object> getSessionAttributes() {
+        return null;
+    }
+
+    @Override
+    public VariablesMap<String, Object> getApplicationAttributes() {
+        return null;
+    }
+
+    @Override
+    public Reader getReader() {
+        return reader;
+    }
+
+    @Override
+    public VariablesMap<String, Object> getVariables() {
+        return variables;
+    }
+
+    @Override
+    public Locale getLocale() {
+        return locale;
+    }
+
+    @Override
+    public void addContextExecutionInfo(String templateName) {
+        Validate.notEmpty(templateName, "Template name cannot be null or empty");
+        final Calendar now = Calendar.getInstance();
+        final WebContextExecutionInfo webContextExecutionInfo = new WebContextExecutionInfo(templateName, now);
+        variables.put(AbstractContext.EXEC_INFO_VARIABLE_NAME, webContextExecutionInfo);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/ThymeleafScriptEngine.java b/src/main/java/org/apache/sling/scripting/thymeleaf/ThymeleafScriptEngine.java
index f21d6d7..91d4916 100644
--- a/src/main/java/org/apache/sling/scripting/thymeleaf/ThymeleafScriptEngine.java
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/ThymeleafScriptEngine.java
@@ -23,22 +23,27 @@ import java.util.Locale;
 
 import javax.script.Bindings;
 import javax.script.ScriptContext;
-import javax.script.ScriptEngineFactory;
 import javax.script.ScriptException;
+import javax.servlet.ServletContext;
 
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
 import org.apache.sling.api.scripting.SlingBindings;
 import org.apache.sling.api.scripting.SlingScriptHelper;
 import org.apache.sling.scripting.api.AbstractSlingScriptEngine;
-import org.thymeleaf.TemplateEngine;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.thymeleaf.context.IContext;
 
 public class ThymeleafScriptEngine extends AbstractSlingScriptEngine {
 
-    private final TemplateEngine templateEngine;
+    private final ThymeleafScriptEngineFactory thymeleafScriptEngineFactory;
 
-    public ThymeleafScriptEngine(final ScriptEngineFactory scriptEngineFactory, final TemplateEngine templateEngine) {
-        super(scriptEngineFactory);
-        this.templateEngine = templateEngine;
+    private final Logger logger = LoggerFactory.getLogger(ThymeleafScriptEngine.class);
+
+    public ThymeleafScriptEngine(final ThymeleafScriptEngineFactory thymeleafScriptEngineFactory) {
+        super(thymeleafScriptEngineFactory);
+        this.thymeleafScriptEngineFactory = thymeleafScriptEngineFactory;
     }
 
     @Override
@@ -50,15 +55,19 @@ public class ThymeleafScriptEngine extends AbstractSlingScriptEngine {
             throw new ScriptException("SlingScriptHelper missing from bindings");
         }
 
-        final Locale locale = helper.getRequest().getLocale();
+        final SlingHttpServletRequest request = helper.getRequest();
+        final SlingHttpServletResponse response = helper.getResponse();
+        final ServletContext servletContext = null; // only used by Thymeleaf's ServletContextResourceResolver
+
+        final Locale locale = helper.getResponse().getLocale();
         final String scriptName = helper.getScript().getScriptResource().getPath();
 
         try {
-            final IContext context = new SlingContext(locale, bindings, reader);
-            templateEngine.process(scriptName, context, scriptContext.getWriter());
+            final IContext context = new SlingWebContext(request, response, servletContext, locale, bindings, reader);
+            thymeleafScriptEngineFactory.getTemplateEngine().process(scriptName, context, scriptContext.getWriter());
         } catch (Exception e) {
-            final String message = String.format("Failure rendering Thymeleaf template '%s': %s", scriptName, e.getMessage());
-            throw new ScriptException(message);
+            logger.error("Failure rendering Thymeleaf template '{}': {}", scriptName, e.getMessage());
+            throw new ScriptException(e);
         }
 
         return null;
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/ThymeleafScriptEngineFactory.java b/src/main/java/org/apache/sling/scripting/thymeleaf/ThymeleafScriptEngineFactory.java
index 781770f..57eb200 100644
--- a/src/main/java/org/apache/sling/scripting/thymeleaf/ThymeleafScriptEngineFactory.java
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/ThymeleafScriptEngineFactory.java
@@ -18,20 +18,36 @@
  */
 package org.apache.sling.scripting.thymeleaf;
 
+import java.util.Dictionary;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
 import javax.script.ScriptEngine;
 
+import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
 import org.apache.felix.scr.annotations.Properties;
 import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
 import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.apache.sling.scripting.api.AbstractScriptEngineFactory;
 import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.messageresolver.IMessageResolver;
+import org.thymeleaf.templateresolver.ITemplateResolver;
 
 @Component(
-    name = "org.apache.sling.scripting.thymeleaf.ThymeleafScriptEngineFactory",
-    label = "%org.apache.sling.scripting.thymeleaf.ThymeleafScriptEngineFactory.label",
-    description = "%org.apache.sling.scripting.thymeleaf.ThymeleafScriptEngineFactory.description",
+    label = "Apache Sling Scripting Thymeleaf “Script Engine Factory”",
+    description = "scripting engine for Thymeleaf templates",
     immediate = true,
     metatype = true
 )
@@ -43,30 +59,105 @@ import org.thymeleaf.TemplateEngine;
 })
 public class ThymeleafScriptEngineFactory extends AbstractScriptEngineFactory {
 
-    private SlingResourceResolver resourceResolver;
-
-    private SlingTemplateResolver templateResolver;
+    @Reference(referenceInterface = ITemplateResolver.class, cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
+    final private Set<ITemplateResolver> templateResolvers = new LinkedHashSet<ITemplateResolver>();
 
-    private SlingMessageResolver messageResolver;
+    @Reference(referenceInterface = IMessageResolver.class, cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
+    final private Set<IMessageResolver> messageResolvers = new LinkedHashSet<IMessageResolver>();
 
     private TemplateEngine templateEngine;
 
+    public static final String DEFAULT_EXTENSION = "html";
+
+    @Property(value = {DEFAULT_EXTENSION}, unbounded = PropertyUnbounded.ARRAY)
+    public static final String EXTENSIONS_PARAMETER = "org.apache.sling.scripting.thymeleaf.extensions";
+
+    public static final String DEFAULT_MIMETYPE = "text/html";
+
+    @Property(value = {DEFAULT_MIMETYPE}, unbounded = PropertyUnbounded.ARRAY)
+    public static final String MIMETYPES_PARAMETER = "org.apache.sling.scripting.thymeleaf.mimetypes";
+
+    public static final String DEFAULT_NAME = "thymeleaf";
+
+    @Property(value = {DEFAULT_NAME}, unbounded = PropertyUnbounded.ARRAY)
+    public static final String NAMES_PARAMETER = "org.apache.sling.scripting.thymeleaf.names";
+
+    public static final String TEMPLATE_CHARSET = "UTF-8";
+
+    private final Logger logger = LoggerFactory.getLogger(ThymeleafScriptEngineFactory.class);
+
     public ThymeleafScriptEngineFactory() {
-        // TODO make configurable
-        setExtensions("html");
-        setMimeTypes("text/html");
-        setNames("thymeleaf");
-        setupThymeleaf();
     }
 
-    // TODO make configurable
-    protected void setupThymeleaf() {
-        resourceResolver = new SlingResourceResolver();
-        templateResolver = new SlingTemplateResolver(resourceResolver);
-        messageResolver = new SlingMessageResolver();
-        templateEngine = new TemplateEngine();
-        templateEngine.setTemplateResolver(templateResolver);
-        templateEngine.setMessageResolver(messageResolver);
+    @Activate
+    private void activate(final ComponentContext componentContext) {
+        logger.debug("activate");
+        configure(componentContext);
+        configureTemplateEngine();
+    }
+
+    @Modified
+    private void modified(final ComponentContext componentContext) {
+        logger.debug("modified");
+        configure(componentContext);
+        configureTemplateEngine();
+    }
+
+    @Deactivate
+    private void deactivate(final ComponentContext componentContext) {
+        logger.debug("deactivate");
+        templateEngine = null;
+    }
+
+    protected void bindTemplateResolvers(final ITemplateResolver templateResolver) {
+        logger.debug("binding template resolver '{}'", templateResolver.getName());
+        templateResolvers.add(templateResolver);
+        configureTemplateEngine();
+    }
+
+    protected void unbindTemplateResolvers(final ITemplateResolver templateResolver) {
+        logger.debug("unbinding template resolver '{}'", templateResolver.getName());
+        templateResolvers.remove(templateResolver);
+        configureTemplateEngine();
+    }
+
+    protected void bindMessageResolvers(final IMessageResolver messageResolver) {
+        logger.debug("binding message resolver '{}'", messageResolver.getName());
+        messageResolvers.add(messageResolver);
+        configureTemplateEngine();
+    }
+
+    protected void unbindMessageResolvers(final IMessageResolver messageResolver) {
+        logger.debug("unbinding message resolver '{}'", messageResolver.getName());
+        messageResolvers.remove(messageResolver);
+        configureTemplateEngine();
+    }
+
+    private synchronized void configure(final ComponentContext componentContext) {
+        final Dictionary properties = componentContext.getProperties();
+
+        final String[] extensions = PropertiesUtil.toStringArray(properties.get(EXTENSIONS_PARAMETER), new String[]{DEFAULT_EXTENSION});
+        setExtensions(extensions);
+
+        final String[] mimeTypes = PropertiesUtil.toStringArray(properties.get(MIMETYPES_PARAMETER), new String[]{DEFAULT_MIMETYPE});
+        setMimeTypes(mimeTypes);
+
+        final String[] names = PropertiesUtil.toStringArray(properties.get(NAMES_PARAMETER), new String[]{DEFAULT_NAME});
+        setNames(names);
+    }
+
+    // the configuration of the Thymeleaf TemplateEngine is static and we need to recreate on modification
+    private synchronized void configureTemplateEngine() {
+        logger.info("configure template engine");
+        if (templateEngine == null || templateEngine.isInitialized()) {
+            templateEngine = new TemplateEngine();
+        }
+        if (templateResolvers.size() > 0) {
+            templateEngine.setTemplateResolvers(templateResolvers);
+        }
+        if (messageResolvers.size() > 0) {
+            templateEngine.setMessageResolvers(messageResolvers);
+        }
     }
 
     @Override
@@ -81,7 +172,12 @@ public class ThymeleafScriptEngineFactory extends AbstractScriptEngineFactory {
 
     @Override
     public ScriptEngine getScriptEngine() {
-        return new ThymeleafScriptEngine(this, templateEngine);
+        logger.debug("get script engine for Thymeleaf");
+        return new ThymeleafScriptEngine(this);
+    }
+
+    TemplateEngine getTemplateEngine() {
+        return templateEngine;
     }
 
 }
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/impl/NonCachingTemplateResolver.java b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/NonCachingTemplateResolver.java
new file mode 100644
index 0000000..4d6af69
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/NonCachingTemplateResolver.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.scripting.thymeleaf.impl;
+
+import java.util.Dictionary;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.scripting.thymeleaf.SlingTemplateModeHandler;
+import org.apache.sling.scripting.thymeleaf.ThymeleafScriptEngineFactory;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.thymeleaf.TemplateProcessingParameters;
+import org.thymeleaf.resourceresolver.IResourceResolver;
+import org.thymeleaf.templateresolver.ITemplateResolutionValidity;
+import org.thymeleaf.templateresolver.ITemplateResolver;
+import org.thymeleaf.templateresolver.NonCacheableTemplateResolutionValidity;
+import org.thymeleaf.templateresolver.TemplateResolution;
+
+@Component(
+    label = "Apache Sling Scripting Thymeleaf “Non-Caching Template Resolver”",
+    description = "non-caching template resolver for Sling Scripting Thymeleaf",
+    immediate = true,
+    metatype = true
+)
+@Service
+@Properties({
+    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
+    @Property(name = Constants.SERVICE_DESCRIPTION, value = "non-caching template resolver for Sling Scripting Thymeleaf")
+})
+public class NonCachingTemplateResolver implements ITemplateResolver {
+
+    @Reference
+    private IResourceResolver resourceResolver;
+
+    @Reference(referenceInterface = SlingTemplateModeHandler.class, cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
+    final private Set<SlingTemplateModeHandler> templateModeHandlers = new LinkedHashSet<SlingTemplateModeHandler>();
+
+    private Integer order;
+
+    public static final int DEFAULT_ORDER = 0;
+
+    @Property(intValue = DEFAULT_ORDER)
+    public static final String ORDER_PARAMETER = "org.apache.sling.scripting.thymeleaf.impl.NonCachingTemplateResolver.order";
+
+    private final Logger logger = LoggerFactory.getLogger(NonCachingTemplateResolver.class);
+
+    public NonCachingTemplateResolver() {
+    }
+
+    @Activate
+    private void activate(final ComponentContext componentContext) {
+        logger.debug("activate");
+        configure(componentContext);
+    }
+
+    @Modified
+    private void modified(final ComponentContext componentContext) {
+        logger.debug("modified");
+        configure(componentContext);
+    }
+
+    @Deactivate
+    private void deactivate(final ComponentContext componentContext) {
+        logger.debug("deactivate");
+    }
+
+    protected void bindTemplateModeHandlers(final SlingTemplateModeHandler templateModeHandler) {
+        logger.debug("binding template mode handler '{}'", templateModeHandler.getTemplateModeName());
+        templateModeHandlers.add(templateModeHandler);
+    }
+
+    protected void unbindTemplateModeHandlers(final SlingTemplateModeHandler templateModeHandler) {
+        logger.debug("unbinding template mode handler '{}'", templateModeHandler.getTemplateModeName());
+        templateModeHandlers.remove(templateModeHandler);
+    }
+
+    private synchronized void configure(final ComponentContext componentContext) {
+        final Dictionary properties = componentContext.getProperties();
+        order = PropertiesUtil.toInteger(properties.get(ORDER_PARAMETER), DEFAULT_ORDER);
+    }
+
+    @Override
+    public String getName() {
+        return getClass().getName();
+    }
+
+    @Override
+    public Integer getOrder() {
+        return order;
+    }
+
+    @Override
+    public TemplateResolution resolveTemplate(TemplateProcessingParameters templateProcessingParameters) {
+        final String templateName = templateProcessingParameters.getTemplateName();
+        final String resourceName = templateName; // TODO
+        final String characterEncoding = ThymeleafScriptEngineFactory.TEMPLATE_CHARSET;
+        final String templateMode = computeTemplateMode(templateName);
+        final ITemplateResolutionValidity validity = new NonCacheableTemplateResolutionValidity();
+        return new TemplateResolution(templateName, resourceName, resourceResolver, characterEncoding, templateMode, validity);
+    }
+
+    @Override
+    public void initialize() {
+    }
+
+    protected String computeTemplateMode(final String templateName) {
+        for (final SlingTemplateModeHandler templateModeHandler : templateModeHandlers) {
+            final String templateMode = templateModeHandler.getTemplateModeName();
+            logger.debug("template mode handler '{}' with patterns {}", templateMode, templateModeHandler.getPatternSpec().getPatterns());
+            if (templateModeHandler.getPatternSpec().matches(templateName)) {
+                logger.debug("using template mode '{}' for template '{}'", templateMode, templateName);
+                return templateMode;
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/impl/ResourceBundleMessageResolver.java b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/ResourceBundleMessageResolver.java
new file mode 100644
index 0000000..fdc4e2b
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/ResourceBundleMessageResolver.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.scripting.thymeleaf.impl;
+
+import java.text.MessageFormat;
+import java.util.Dictionary;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.i18n.ResourceBundleProvider;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.thymeleaf.Arguments;
+import org.thymeleaf.context.IContext;
+import org.thymeleaf.messageresolver.IMessageResolver;
+import org.thymeleaf.messageresolver.MessageResolution;
+
+@Component(
+    label = "Apache Sling Scripting Thymeleaf “Resource Bundle Message Resolver”",
+    description = "resource bundle message resolver for Sling Scripting Thymeleaf",
+    immediate = true,
+    metatype = true
+)
+@Service
+@Properties({
+    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
+    @Property(name = Constants.SERVICE_DESCRIPTION, value = "resource bundle message resolver for Sling Scripting Thymeleaf")
+})
+public class ResourceBundleMessageResolver implements IMessageResolver {
+
+    @Reference
+    private ResourceBundleProvider resourceBundleProvider;
+
+    private Integer order;
+
+    public static final int DEFAULT_ORDER = 0;
+
+    @Property(intValue = DEFAULT_ORDER)
+    public static final String ORDER_PARAMETER = "org.apache.sling.scripting.thymeleaf.impl.ResourceBundleMessageResolver.order";
+
+    public static final Object[] EMPTY_MESSAGE_PARAMETERS = new Object[0];
+
+    private final Logger logger = LoggerFactory.getLogger(ResourceBundleMessageResolver.class);
+
+    public ResourceBundleMessageResolver() {
+    }
+
+    @Activate
+    private void activate(final ComponentContext componentContext) {
+        logger.debug("activate");
+        configure(componentContext);
+    }
+
+    @Modified
+    private void modified(final ComponentContext componentContext) {
+        logger.debug("modified");
+        configure(componentContext);
+    }
+
+    @Deactivate
+    private void deactivate(final ComponentContext componentContext) {
+        logger.debug("deactivate");
+    }
+
+    private synchronized void configure(final ComponentContext componentContext) {
+        final Dictionary properties = componentContext.getProperties();
+        order = PropertiesUtil.toInteger(properties.get(ORDER_PARAMETER), DEFAULT_ORDER);
+    }
+
+    @Override
+    public String getName() {
+        return getClass().getName();
+    }
+
+    @Override
+    public Integer getOrder() {
+        return order;
+    }
+
+    @Override
+    public MessageResolution resolveMessage(final Arguments arguments, final String key, final Object[] messageParameters) {
+        logger.debug("arguments: {}, key: {}, message parameters: {}", arguments, key, messageParameters);
+        final IContext context = arguments.getContext();
+        final Locale locale = context.getLocale();
+        final ResourceBundle resourceBundle = resourceBundleProvider.getResourceBundle(locale);
+        final String string = resourceBundle.getString(key);
+        final MessageFormat messageFormat = new MessageFormat(string, locale);
+        final String message = messageFormat.format((messageParameters != null ? messageParameters : EMPTY_MESSAGE_PARAMETERS));
+        return new MessageResolution(message);
+    }
+
+    @Override
+    public void initialize() {
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/SlingResourceResolver.java b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/ScriptReaderResourceResolver.java
similarity index 61%
rename from src/main/java/org/apache/sling/scripting/thymeleaf/SlingResourceResolver.java
rename to src/main/java/org/apache/sling/scripting/thymeleaf/impl/ScriptReaderResourceResolver.java
index 51eea3e..661ed9e 100644
--- a/src/main/java/org/apache/sling/scripting/thymeleaf/SlingResourceResolver.java
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/ScriptReaderResourceResolver.java
@@ -16,23 +16,41 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.scripting.thymeleaf;
+package org.apache.sling.scripting.thymeleaf.impl;
 
 import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
 
 import org.apache.commons.io.input.ReaderInputStream;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.scripting.thymeleaf.SlingContext;
+import org.apache.sling.scripting.thymeleaf.ThymeleafScriptEngineFactory;
+import org.osgi.framework.Constants;
 import org.thymeleaf.TemplateProcessingParameters;
 import org.thymeleaf.context.IContext;
 import org.thymeleaf.exceptions.TemplateProcessingException;
 import org.thymeleaf.resourceresolver.IResourceResolver;
 import org.thymeleaf.util.Validate;
 
-public class SlingResourceResolver implements IResourceResolver {
+@Component(
+    label = "Apache Sling Scripting Thymeleaf “Script Reader Resource Resolver”",
+    description = "script reader resource resolver for Sling Scripting Thymeleaf",
+    immediate = true,
+    metatype = true
+)
+@Service
+@Properties({
+    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
+    @Property(name = Constants.SERVICE_DESCRIPTION, value = "script reader resource resolver for Sling Scripting Thymeleaf"),
+    @Property(name = Constants.SERVICE_RANKING, intValue = 0, propertyPrivate = false)
+})
+public class ScriptReaderResourceResolver implements IResourceResolver {
 
     @Override
     public String getName() {
-        return getClass().getSimpleName();
+        return getClass().getName();
     }
 
     @Override
@@ -43,7 +61,7 @@ public class SlingResourceResolver implements IResourceResolver {
         final IContext context = templateProcessingParameters.getContext();
         if (context instanceof SlingContext) {
             final SlingContext slingContext = (SlingContext) context;
-            return new ReaderInputStream(slingContext.getReader(), StandardCharsets.UTF_8);
+            return new ReaderInputStream(slingContext.getReader(), ThymeleafScriptEngineFactory.TEMPLATE_CHARSET);
         } else {
             throw new TemplateProcessingException("Cannot handle context: " + context.getClass().getName());
         }
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/AbstractTemplateModeHandler.java b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/AbstractTemplateModeHandler.java
new file mode 100644
index 0000000..589a2b5
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/AbstractTemplateModeHandler.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.scripting.thymeleaf.impl.templatemodehandler;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.sling.scripting.thymeleaf.SlingTemplateModeHandler;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.thymeleaf.PatternSpec;
+import org.thymeleaf.templateparser.ITemplateParser;
+import org.thymeleaf.templatewriter.ITemplateWriter;
+
+public abstract class AbstractTemplateModeHandler implements SlingTemplateModeHandler {
+
+    private final String templateModeName;
+
+    private final ITemplateParser templateParser;
+
+    private final ITemplateWriter templateWriter;
+
+    private PatternSpec patternSpec;
+
+    // see StandardTemplateModeHandlers#MAX_PARSERS_POOL_SIZE
+    private static final int MAX_PARSERS_POOL_SIZE = 24;
+
+    private final Logger logger = LoggerFactory.getLogger(AbstractTemplateModeHandler.class);
+
+    protected AbstractTemplateModeHandler(final String templateModeName, final ITemplateParser templateParser, final ITemplateWriter templateWriter) {
+        this.templateModeName = templateModeName;
+        this.templateParser = templateParser;
+        this.templateWriter = templateWriter;
+    }
+
+    @Activate
+    protected void activate(final ComponentContext componentContext) {
+        logger.debug("activate");
+        configure(componentContext);
+    }
+
+    @Modified
+    protected void modified(final ComponentContext componentContext) {
+        logger.debug("modified");
+        configure(componentContext);
+    }
+
+    @Deactivate
+    protected void deactivate(final ComponentContext componentContext) {
+        logger.debug("deactivate");
+    }
+
+    protected abstract void configure(final ComponentContext componentContext);
+
+    protected synchronized void configurePatternSpec(final String[] strings) {
+        final Set<String> set = new HashSet<String>();
+        Collections.addAll(set, strings);
+        final PatternSpec patternSpec = new PatternSpec(); // isInitialized() is private, so create a new PatternSpec
+        patternSpec.setPatterns(set);
+        this.patternSpec = patternSpec;
+    }
+
+    @Override
+    public String getTemplateModeName() {
+        return templateModeName;
+    }
+
+    @Override
+    public ITemplateParser getTemplateParser() {
+        return templateParser;
+    }
+
+    @Override
+    public ITemplateWriter getTemplateWriter() {
+        return templateWriter;
+    }
+
+    @Override
+    public PatternSpec getPatternSpec() {
+        return patternSpec;
+    }
+
+    // see StandardTemplateModeHandlers
+    protected static int poolSize() {
+        final int availableProcessors = Runtime.getRuntime().availableProcessors();
+        return Math.min((availableProcessors <= 2 ? availableProcessors : availableProcessors - 1), MAX_PARSERS_POOL_SIZE);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/Html5TemplateModeHandler.java b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/Html5TemplateModeHandler.java
new file mode 100644
index 0000000..438ce38
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/Html5TemplateModeHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.scripting.thymeleaf.impl.templatemodehandler;
+
+import java.util.Dictionary;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.thymeleaf.templateparser.xmlsax.XhtmlAndHtml5NonValidatingSAXTemplateParser;
+import org.thymeleaf.templatewriter.XhtmlHtml5TemplateWriter;
+
+@Component(
+    label = "Apache Sling Scripting Thymeleaf “HTML5 Template Mode Handler”",
+    description = "HTML5 template mode handler for Sling Scripting Thymeleaf",
+    immediate = true,
+    metatype = true
+)
+@Service
+@Properties({
+    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
+    @Property(name = Constants.SERVICE_DESCRIPTION, value = "HTML5 template mode handler for Sling Scripting Thymeleaf")
+})
+public class Html5TemplateModeHandler extends AbstractTemplateModeHandler {
+
+    public static final String TEMPLATE_MODE_NAME = "HTML5";
+
+    public static final String DEFAULT_PATTERN = "";
+
+    @Property(value = {DEFAULT_PATTERN}, unbounded = PropertyUnbounded.ARRAY)
+    public static final String PATTERNS_PARAMETER = "org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.Html5TemplateModeHandler.patterns";
+
+    public Html5TemplateModeHandler() {
+        super(TEMPLATE_MODE_NAME, new XhtmlAndHtml5NonValidatingSAXTemplateParser(poolSize()), new XhtmlHtml5TemplateWriter());
+    }
+
+    protected synchronized void configure(final ComponentContext componentContext) {
+        final Dictionary properties = componentContext.getProperties();
+        final String[] strings = PropertiesUtil.toStringArray(properties.get(PATTERNS_PARAMETER), new String[]{DEFAULT_PATTERN});
+        configurePatternSpec(strings);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/LegacyHtml5TemplateModeHandler.java b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/LegacyHtml5TemplateModeHandler.java
new file mode 100644
index 0000000..8591d5c
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/LegacyHtml5TemplateModeHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.scripting.thymeleaf.impl.templatemodehandler;
+
+import java.util.Dictionary;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.thymeleaf.templateparser.html.LegacyHtml5TemplateParser;
+import org.thymeleaf.templatewriter.XmlTemplateWriter;
+
+@Component(
+    label = "Apache Sling Scripting Thymeleaf “Legacy HTML5 Template Mode Handler”",
+    description = "legacy HTML5 template mode handler for Sling Scripting Thymeleaf",
+    immediate = true,
+    metatype = true
+)
+@Service
+@Properties({
+    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
+    @Property(name = Constants.SERVICE_DESCRIPTION, value = "legacy HTML5 template mode handler for Sling Scripting Thymeleaf")
+})
+public class LegacyHtml5TemplateModeHandler extends AbstractTemplateModeHandler {
+
+    public static final String TEMPLATE_MODE_NAME = "LEGACYHTML5";
+
+    public static final String DEFAULT_PATTERN = "*.html";
+
+    @Property(value = {DEFAULT_PATTERN}, unbounded = PropertyUnbounded.ARRAY)
+    public static final String PATTERNS_PARAMETER = "org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.LegacyHtml5TemplateModeHandler.patterns";
+
+    public LegacyHtml5TemplateModeHandler() {
+        super(TEMPLATE_MODE_NAME, new LegacyHtml5TemplateParser(TEMPLATE_MODE_NAME, poolSize()), new XmlTemplateWriter());
+    }
+
+    protected synchronized void configure(final ComponentContext componentContext) {
+        final Dictionary properties = componentContext.getProperties();
+        final String[] strings = PropertiesUtil.toStringArray(properties.get(PATTERNS_PARAMETER), new String[]{DEFAULT_PATTERN});
+        configurePatternSpec(strings);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/ValidatingXhtmlTemplateModeHandler.java b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/ValidatingXhtmlTemplateModeHandler.java
new file mode 100644
index 0000000..14e0942
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/ValidatingXhtmlTemplateModeHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.scripting.thymeleaf.impl.templatemodehandler;
+
+import java.util.Dictionary;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.thymeleaf.templateparser.xmlsax.XhtmlAndHtml5NonValidatingSAXTemplateParser;
+import org.thymeleaf.templatewriter.XhtmlHtml5TemplateWriter;
+
+@Component(
+    label = "Apache Sling Scripting Thymeleaf “Validating XHTML Template Mode Handler”",
+    description = "validating XHTML template mode handler for Sling Scripting Thymeleaf",
+    immediate = true,
+    metatype = true
+)
+@Service
+@Properties({
+    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
+    @Property(name = Constants.SERVICE_DESCRIPTION, value = "validating XHTML template mode handler for Sling Scripting Thymeleaf")
+})
+public class ValidatingXhtmlTemplateModeHandler extends AbstractTemplateModeHandler {
+
+    public static final String TEMPLATE_MODE_NAME = "VALIDXHTML";
+
+    public static final String DEFAULT_PATTERN = "*.xhtml";
+
+    @Property(value = {DEFAULT_PATTERN}, unbounded = PropertyUnbounded.ARRAY)
+    public static final String PATTERNS_PARAMETER = "org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.ValidatingXhtmlTemplateModeHandler.patterns";
+
+    public ValidatingXhtmlTemplateModeHandler() {
+        super(TEMPLATE_MODE_NAME, new XhtmlAndHtml5NonValidatingSAXTemplateParser(poolSize()), new XhtmlHtml5TemplateWriter());
+    }
+
+    protected synchronized void configure(final ComponentContext componentContext) {
+        final Dictionary properties = componentContext.getProperties();
+        final String[] strings = PropertiesUtil.toStringArray(properties.get(PATTERNS_PARAMETER), new String[]{DEFAULT_PATTERN});
+        configurePatternSpec(strings);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/ValidatingXmlTemplateModeHandler.java b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/ValidatingXmlTemplateModeHandler.java
new file mode 100644
index 0000000..17ac0f0
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/ValidatingXmlTemplateModeHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.scripting.thymeleaf.impl.templatemodehandler;
+
+import java.util.Dictionary;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.thymeleaf.templateparser.xmlsax.XmlValidatingSAXTemplateParser;
+import org.thymeleaf.templatewriter.XmlTemplateWriter;
+
+@Component(
+    label = "Apache Sling Scripting Thymeleaf “Validating XML Template Mode Handler”",
+    description = "validating XML template mode handler for Sling Scripting Thymeleaf",
+    immediate = true,
+    metatype = true
+)
+@Service
+@Properties({
+    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
+    @Property(name = Constants.SERVICE_DESCRIPTION, value = "validating XML template mode handler for Sling Scripting Thymeleaf")
+})
+public class ValidatingXmlTemplateModeHandler extends AbstractTemplateModeHandler {
+
+    public static final String TEMPLATE_MODE_NAME = "VALIDXML";
+
+    public static final String DEFAULT_PATTERN = "*.xml";
+
+    @Property(value = {DEFAULT_PATTERN}, unbounded = PropertyUnbounded.ARRAY)
+    public static final String PATTERNS_PARAMETER = "org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.ValidatingXmlTemplateModeHandler.patterns";
+
+    public ValidatingXmlTemplateModeHandler() {
+        super(TEMPLATE_MODE_NAME, new XmlValidatingSAXTemplateParser(poolSize()), new XmlTemplateWriter());
+    }
+
+    protected synchronized void configure(final ComponentContext componentContext) {
+        final Dictionary properties = componentContext.getProperties();
+        final String[] strings = PropertiesUtil.toStringArray(properties.get(PATTERNS_PARAMETER), new String[]{DEFAULT_PATTERN});
+        configurePatternSpec(strings);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/XhtmlTemplateModeHandler.java b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/XhtmlTemplateModeHandler.java
new file mode 100644
index 0000000..3f01374
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/XhtmlTemplateModeHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.scripting.thymeleaf.impl.templatemodehandler;
+
+import java.util.Dictionary;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.thymeleaf.templateparser.xmlsax.XhtmlAndHtml5NonValidatingSAXTemplateParser;
+import org.thymeleaf.templatewriter.XhtmlHtml5TemplateWriter;
+
+@Component(
+    label = "Apache Sling Scripting Thymeleaf “XHTML Template Mode Handler”",
+    description = "XHTML template mode handler for Sling Scripting Thymeleaf",
+    immediate = true,
+    metatype = true
+)
+@Service
+@Properties({
+    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
+    @Property(name = Constants.SERVICE_DESCRIPTION, value = "XHTML template mode handler for Sling Scripting Thymeleaf")
+})
+public class XhtmlTemplateModeHandler extends AbstractTemplateModeHandler {
+
+    public static final String TEMPLATE_MODE_NAME = "XHTML";
+
+    public static final String DEFAULT_PATTERN = "*.xhtml";
+
+    @Property(value = {DEFAULT_PATTERN}, unbounded = PropertyUnbounded.ARRAY)
+    public static final String PATTERNS_PARAMETER = "org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.XhtmlTemplateModeHandler.patterns";
+
+    public XhtmlTemplateModeHandler() {
+        super(TEMPLATE_MODE_NAME, new XhtmlAndHtml5NonValidatingSAXTemplateParser(poolSize()), new XhtmlHtml5TemplateWriter());
+    }
+
+    protected synchronized void configure(final ComponentContext componentContext) {
+        final Dictionary properties = componentContext.getProperties();
+        final String[] strings = PropertiesUtil.toStringArray(properties.get(PATTERNS_PARAMETER), new String[]{DEFAULT_PATTERN});
+        configurePatternSpec(strings);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/XmlTemplateModeHandler.java b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/XmlTemplateModeHandler.java
new file mode 100644
index 0000000..09b9eaf
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/impl/templatemodehandler/XmlTemplateModeHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.scripting.thymeleaf.impl.templatemodehandler;
+
+import java.util.Dictionary;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.thymeleaf.templateparser.xmlsax.XmlNonValidatingSAXTemplateParser;
+import org.thymeleaf.templatewriter.XmlTemplateWriter;
+
+@Component(
+    label = "Apache Sling Scripting Thymeleaf “XML Template Mode Handler”",
+    description = "XML template mode handler for Sling Scripting Thymeleaf",
+    immediate = true,
+    metatype = true
+)
+@Service
+@Properties({
+    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
+    @Property(name = Constants.SERVICE_DESCRIPTION, value = "XML template mode handler for Sling Scripting Thymeleaf")
+})
+public class XmlTemplateModeHandler extends AbstractTemplateModeHandler {
+
+    public static final String TEMPLATE_MODE_NAME = "XML";
+
+    public static final String DEFAULT_PATTERN = "*.xml";
+
+    @Property(value = {DEFAULT_PATTERN}, unbounded = PropertyUnbounded.ARRAY)
+    public static final String PATTERNS_PARAMETER = "org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.XmlTemplateModeHandler.patterns";
+
+    public XmlTemplateModeHandler() {
+        super(TEMPLATE_MODE_NAME, new XmlNonValidatingSAXTemplateParser(poolSize()), new XmlTemplateWriter());
+    }
+
+    protected synchronized void configure(final ComponentContext componentContext) {
+        final Dictionary properties = componentContext.getProperties();
+        final String[] strings = PropertiesUtil.toStringArray(properties.get(PATTERNS_PARAMETER), new String[]{DEFAULT_PATTERN});
+        configurePatternSpec(strings);
+    }
+
+}
diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties
index b86149e..bc3e542 100644
--- a/src/main/resources/OSGI-INF/metatype/metatype.properties
+++ b/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -20,5 +20,44 @@
 service.ranking.name = service ranking
 service.ranking.description = service property for identifying the service's ranking number
 
-org.apache.sling.scripting.thymeleaf.ThymeleafScriptEngineFactory.label = Apache Sling Scripting Thymeleaf
-org.apache.sling.scripting.thymeleaf.ThymeleafScriptEngineFactory.description = scripting engine for Thymeleaf templates
+# ThymeleafScriptEngineFactory
+org.apache.sling.scripting.thymeleaf.extensions.name = extensions
+org.apache.sling.scripting.thymeleaf.extensions.description = extensions
+
+org.apache.sling.scripting.thymeleaf.mimetypes.name = mime types
+org.apache.sling.scripting.thymeleaf.mimetypes.description = mime types
+
+org.apache.sling.scripting.thymeleaf.names.name = names
+org.apache.sling.scripting.thymeleaf.names.description = names
+
+# NonCachingTemplateResolver
+org.apache.sling.scripting.thymeleaf.impl.NonCachingTemplateResolver.order.name = order
+org.apache.sling.scripting.thymeleaf.impl.NonCachingTemplateResolver.order.description = property for ordering template resolvers inside the Thymeleaf template engine
+
+# ResourceBundleMessageResolver
+org.apache.sling.scripting.thymeleaf.impl.ResourceBundleMessageResolver.order.name = order
+org.apache.sling.scripting.thymeleaf.impl.ResourceBundleMessageResolver.order.description = property for ordering message resolvers inside the Thymeleaf template engine
+
+# XmlTemplateModeHandler
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.XmlTemplateModeHandler.patterns.name = patterns
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.XmlTemplateModeHandler.patterns.description = TODO
+
+# ValidatingXmlTemplateModeHandler
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.ValidatingXmlTemplateModeHandler.patterns.name = patterns
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.ValidatingXmlTemplateModeHandler.patterns.description = TODO
+
+# XhtmlTemplateModeHandler
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.XhtmlTemplateModeHandler.patterns.name = patterns
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.XhtmlTemplateModeHandler.patterns.description = TODO
+
+# ValidatingXhtmlTemplateModeHandler
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.ValidatingXhtmlTemplateModeHandler.patterns.name = patterns
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.ValidatingXhtmlTemplateModeHandler.patterns.description = TODO
+
+# Html5TemplateModeHandler
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.Html5TemplateModeHandler.patterns.name = patterns
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.Html5TemplateModeHandler.patterns.description = TODO
+
+# LegacyHtml5TemplateModeHandler
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.LegacyHtml5TemplateModeHandler.patterns.name = patterns
+org.apache.sling.scripting.thymeleaf.impl.templatemodehandler.LegacyHtml5TemplateModeHandler.patterns.description = TODO

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.