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:14:20 UTC

[sling-org-apache-sling-scripting-thymeleaf] 18/39: SLING-5928 Use Service ResourceResolver in SlingResourceTemplateResolver

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

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

commit 08237880e5c1f425ae4c3abfdccb896eac881380
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Sun Oct 23 17:38:16 2016 +0000

    SLING-5928 Use Service ResourceResolver in SlingResourceTemplateResolver
    
    * (re)add and use Sling contexts
    * add a Sling engine context factory
    * add preliminary request-scoped resource resolver provider (SLING-6165)
    * improve logging in tests
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/scripting/org.apache.sling.scripting.thymeleaf@1766307 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  13 +-
 .../scripting/thymeleaf/DefaultSlingContext.java   |  41 ++++
 .../sling/scripting/thymeleaf/SlingContext.java    |  30 +++
 .../thymeleaf/internal/SlingEngineContext.java     |  44 ++++
 .../internal/SlingEngineContextFactory.java        |  63 ++++++
 .../internal/SlingResourceTemplateResolver.java    |  26 ++-
 .../thymeleaf/internal/SlingWebContext.java        |  70 +++++++
 .../thymeleaf/internal/ThymeleafScriptEngine.java  |  12 +-
 .../internal/ThymeleafScriptEngineFactory.java     |  19 +-
 .../ThymeleafScriptEngineFactoryConfiguration.java |   2 +-
 .../DelegatingResourceResolver.java                | 231 +++++++++++++++++++++
 .../RequestScopedResourceResolverProvider.java     |  90 ++++++++
 .../thymeleaf/it/tests/ThymeleafTestSupport.java   |  40 +++-
 src/test/resources/exam.properties                 |  19 ++
 src/test/resources/logback.xml                     |  30 +++
 src/test/resources/repoinit.txt                    |  34 +++
 16 files changed, 747 insertions(+), 17 deletions(-)

diff --git a/pom.xml b/pom.xml
index e71dff2..6db782c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -138,6 +138,12 @@
     </dependency>
     <dependency>
       <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.serviceusermapper</artifactId>
+      <version>1.2.2</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.sling</groupId>
       <artifactId>org.apache.sling.scripting.api</artifactId>
       <version>2.1.8</version>
       <scope>provided</scope>
@@ -151,7 +157,7 @@
     <dependency>
       <groupId>org.apache.sling</groupId>
       <artifactId>org.apache.sling.testing.paxexam</artifactId>
-      <version>0.0.2</version>
+      <version>0.0.3-SNAPSHOT</version>
       <scope>provided</scope>
     </dependency>
     <!-- Thymeleaf -->
@@ -192,6 +198,11 @@
       <version>1.9.1</version>
       <scope>test</scope>
     </dependency>
+    <!-- jsr305 -->
+    <dependency>
+      <groupId>com.google.code.findbugs</groupId>
+      <artifactId>jsr305</artifactId>
+    </dependency>
     <!-- logging -->
     <dependency>
       <groupId>org.slf4j</groupId>
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/DefaultSlingContext.java b/src/main/java/org/apache/sling/scripting/thymeleaf/DefaultSlingContext.java
new file mode 100644
index 0000000..f6d4d1d
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/DefaultSlingContext.java
@@ -0,0 +1,41 @@
+/*
+ * 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.util.Locale;
+import java.util.Map;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.thymeleaf.context.AbstractContext;
+
+public class DefaultSlingContext extends AbstractContext implements SlingContext {
+
+    private final ResourceResolver resourceResolver;
+
+    public DefaultSlingContext(final ResourceResolver resourceResolver, final Locale locale, final Map<String, Object> variables) {
+        super(locale, variables);
+        this.resourceResolver = resourceResolver;
+    }
+
+    @Override
+    public ResourceResolver getResourceResolver() {
+        return resourceResolver;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/SlingContext.java b/src/main/java/org/apache/sling/scripting/thymeleaf/SlingContext.java
new file mode 100644
index 0000000..4b2c282
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/SlingContext.java
@@ -0,0 +1,30 @@
+/*
+ * 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 org.apache.sling.api.resource.ResourceResolver;
+import org.osgi.annotation.versioning.ProviderType;
+import org.thymeleaf.context.IContext;
+
+@ProviderType
+public interface SlingContext extends IContext {
+
+    ResourceResolver getResourceResolver();
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingEngineContext.java b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingEngineContext.java
new file mode 100644
index 0000000..c5dc3b6
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingEngineContext.java
@@ -0,0 +1,44 @@
+/*
+ * 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.internal;
+
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.scripting.thymeleaf.SlingContext;
+import org.thymeleaf.IEngineConfiguration;
+import org.thymeleaf.context.EngineContext;
+import org.thymeleaf.engine.TemplateData;
+
+public class SlingEngineContext extends EngineContext implements SlingContext {
+
+    private final ResourceResolver resourceResolver;
+
+    public SlingEngineContext(final ResourceResolver resourceResolver, final IEngineConfiguration configuration, final TemplateData templateData, final Map<String, Object> templateResolutionAttributes, final Locale locale, final Map<String, Object> variables) {
+        super(configuration, templateData, templateResolutionAttributes, locale, variables);
+        this.resourceResolver = resourceResolver;
+    }
+
+    @Override
+    public ResourceResolver getResourceResolver() {
+        return resourceResolver;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingEngineContextFactory.java b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingEngineContextFactory.java
new file mode 100644
index 0000000..e25715e
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingEngineContextFactory.java
@@ -0,0 +1,63 @@
+/*
+ * 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.internal;
+
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.scripting.thymeleaf.SlingContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.Component;
+import org.thymeleaf.IEngineConfiguration;
+import org.thymeleaf.context.IContext;
+import org.thymeleaf.context.IEngineContext;
+import org.thymeleaf.context.IEngineContextFactory;
+import org.thymeleaf.engine.TemplateData;
+
+@Component(
+    immediate = true,
+    property = {
+        Constants.SERVICE_DESCRIPTION + "=Sling EngineContextFactory for Sling Scripting Thymeleaf",
+        Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
+    }
+)
+public class SlingEngineContextFactory implements IEngineContextFactory {
+
+    @Override
+    public IEngineContext createEngineContext(final IEngineConfiguration configuration, final TemplateData templateData, final Map<String, Object> templateResolutionAttributes, final IContext context) {
+        if (context instanceof SlingContext) {
+            // TODO web context
+            final SlingContext slingContext = (SlingContext) context;
+            final ResourceResolver resourceResolver = slingContext.getResourceResolver();
+            final Locale locale = context.getLocale();
+            final Set<String> variableNames = context.getVariableNames();
+            final Map<String, Object> variables = new LinkedHashMap<>(variableNames.size() + 1, 1.0f);
+            for (final String variableName : variableNames) {
+                variables.put(variableName, context.getVariable(variableName));
+            }
+            return new SlingEngineContext(resourceResolver, configuration, templateData, templateResolutionAttributes, locale, variables);
+        } else {
+            throw new IllegalStateException("context is not an instance of SlingContext");
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingResourceTemplateResolver.java b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingResourceTemplateResolver.java
index 5f53242..790cfdc 100644
--- a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingResourceTemplateResolver.java
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingResourceTemplateResolver.java
@@ -22,7 +22,7 @@ import java.util.Map;
 
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.scripting.thymeleaf.SlingContext;
 import org.apache.sling.scripting.thymeleaf.TemplateModeProvider;
 import org.osgi.framework.Constants;
 import org.osgi.service.component.annotations.Activate;
@@ -111,15 +111,21 @@ public class SlingResourceTemplateResolver implements ITemplateResolver {
     @Override
     public TemplateResolution resolveTemplate(final IEngineConfiguration engineConfiguration, final IContext context, final String ownerTemplate, final String template, final Map<String, Object> templateResolutionAttributes) {
         logger.debug("resolving template '{}'", template);
-        final ResourceResolver resourceResolver = (ResourceResolver) context.getVariable(SlingBindings.RESOLVER);
-        final Resource resource = resourceResolver.getResource(template);
-        final ITemplateResource templateResource = new SlingTemplateResource(resource);
-        final boolean templateResourceExistenceVerified = false;
-        final TemplateMode templateMode = templateModeProvider.provideTemplateMode(resource);
-        logger.debug("using template mode {} for template '{}'", templateMode, template);
-        final boolean useDecoupledLogic = templateMode.isMarkup() && configuration.useDecoupledLogic();
-        final ICacheEntryValidity validity = NonCacheableCacheEntryValidity.INSTANCE;
-        return new TemplateResolution(templateResource, templateResourceExistenceVerified, templateMode, useDecoupledLogic, validity);
+        if (context instanceof SlingContext) {
+            final SlingContext slingContext = (SlingContext) context;
+            final ResourceResolver resourceResolver = slingContext.getResourceResolver();
+            final Resource resource = resourceResolver.getResource(template);
+            final ITemplateResource templateResource = new SlingTemplateResource(resource);
+            final boolean templateResourceExistenceVerified = false;
+            final TemplateMode templateMode = templateModeProvider.provideTemplateMode(resource);
+            logger.debug("using template mode {} for template '{}'", templateMode, template);
+            final boolean useDecoupledLogic = templateMode.isMarkup() && configuration.useDecoupledLogic();
+            final ICacheEntryValidity validity = NonCacheableCacheEntryValidity.INSTANCE;
+            return new TemplateResolution(templateResource, templateResourceExistenceVerified, templateMode, useDecoupledLogic, validity);
+        } else {
+            logger.error("context is not an instance of SlingContext");
+            return null;
+        }
     }
 
 }
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingWebContext.java b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingWebContext.java
new file mode 100644
index 0000000..e977d46
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/SlingWebContext.java
@@ -0,0 +1,70 @@
+/*
+ * 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.internal;
+
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.scripting.thymeleaf.DefaultSlingContext;
+import org.thymeleaf.context.IWebContext;
+
+public class SlingWebContext extends DefaultSlingContext implements IWebContext {
+
+    private final SlingHttpServletRequest servletRequest;
+
+    private final SlingHttpServletResponse servletResponse;
+
+    private final ServletContext servletContext;
+
+    public SlingWebContext(final SlingHttpServletRequest servletRequest, final SlingHttpServletResponse servletResponse, final ServletContext servletContext, final ResourceResolver resourceResolver, final Locale locale, final Map<String, Object> variables) {
+        super(resourceResolver, locale, variables);
+        this.servletRequest = servletRequest;
+        this.servletResponse = servletResponse;
+        this.servletContext = servletContext;
+    }
+
+    @Override
+    public HttpServletRequest getRequest() {
+        return servletRequest;
+    }
+
+    @Override
+    public HttpServletResponse getResponse() {
+        return servletResponse;
+    }
+
+    @Override
+    public HttpSession getSession() {
+        return servletRequest.getSession(false);
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return servletContext;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngine.java b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngine.java
index 80ea877..d653556 100644
--- a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngine.java
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngine.java
@@ -25,13 +25,16 @@ import java.util.Locale;
 import javax.script.Bindings;
 import javax.script.ScriptContext;
 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.resource.ResourceResolver;
 import org.apache.sling.api.scripting.SlingBindings;
 import org.apache.sling.api.scripting.SlingScriptHelper;
 import org.apache.sling.scripting.api.AbstractSlingScriptEngine;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.thymeleaf.context.Context;
 import org.thymeleaf.context.IContext;
 
 public final class ThymeleafScriptEngine extends AbstractSlingScriptEngine {
@@ -54,12 +57,17 @@ public final class ThymeleafScriptEngine extends AbstractSlingScriptEngine {
             throw new ScriptException("SlingScriptHelper missing from bindings");
         }
 
+        final SlingHttpServletRequest request = helper.getRequest();
+        final SlingHttpServletResponse response = helper.getResponse();
+        final ServletContext servletContext = null; // only used by Thymeleaf's ServletContextResourceResolver (TODO check if still true for 3.0)
+
         final Locale locale = helper.getResponse().getLocale();
         final String scriptName = helper.getScript().getScriptResource().getPath();
         final Writer writer = scriptContext.getWriter();
 
         try {
-            final IContext context = new Context(locale, bindings);
+            final ResourceResolver resourceResolver = thymeleafScriptEngineFactory.getRequestScopedResourceResolver();
+            final IContext context = new SlingWebContext(request, response, servletContext, resourceResolver, locale, bindings);
             thymeleafScriptEngineFactory.getTemplateEngine().process(scriptName, context, writer);
         } catch (Exception e) {
             logger.error("Failure rendering Thymeleaf template '{}': {}", scriptName, e.getMessage());
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngineFactory.java b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngineFactory.java
index f475925..846b210 100644
--- a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngineFactory.java
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngineFactory.java
@@ -28,7 +28,9 @@ import java.util.Set;
 import javax.script.ScriptEngine;
 import javax.script.ScriptEngineFactory;
 
+import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.scripting.api.AbstractScriptEngineFactory;
+import org.apache.sling.scripting.thymeleaf.internal.resourceresolver.RequestScopedResourceResolverProvider;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceRegistration;
@@ -120,7 +122,6 @@ public final class ThymeleafScriptEngineFactory extends AbstractScriptEngineFact
     private volatile ICacheManager cacheManager;
 
     @Reference(
-        cardinality = ReferenceCardinality.OPTIONAL,
         policy = ReferencePolicy.DYNAMIC,
         policyOption = ReferencePolicyOption.GREEDY,
         bind = "setEngineContextFactory",
@@ -128,6 +129,12 @@ public final class ThymeleafScriptEngineFactory extends AbstractScriptEngineFact
     )
     private volatile IEngineContextFactory engineContextFactory;
 
+    @Reference(
+        policy = ReferencePolicy.DYNAMIC,
+        policyOption = ReferencePolicyOption.GREEDY
+    )
+    private volatile RequestScopedResourceResolverProvider resourceResolverProvider;
+
     private ThymeleafScriptEngineFactoryConfiguration configuration;
 
     private BundleContext bundleContext;
@@ -338,6 +345,12 @@ public final class ThymeleafScriptEngineFactory extends AbstractScriptEngineFact
             logger.info("configuration is null, not setting up new template engine");
             return;
         }
+
+        if (!configuration.useStandardEngineContextFactory() && engineContextFactory == null) {
+            logger.info("no engine context factory available, not setting up new template engine");
+            return;
+        }
+
         // setup template engine
         final TemplateEngine templateEngine = new TemplateEngine();
         // Template Resolvers
@@ -415,4 +428,8 @@ public final class ThymeleafScriptEngineFactory extends AbstractScriptEngineFact
         }
     }
 
+    ResourceResolver getRequestScopedResourceResolver() {
+        return resourceResolverProvider.getResourceResolver();
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngineFactoryConfiguration.java b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngineFactoryConfiguration.java
index 3830241..e73c710 100644
--- a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngineFactoryConfiguration.java
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/ThymeleafScriptEngineFactoryConfiguration.java
@@ -92,6 +92,6 @@ import org.osgi.service.metatype.annotations.ObjectClassDefinition;
         name = "use standard engine context factory",
         description = "Enables Thymeleaf's standard engine context factory and uses it exclusively."
     )
-    boolean useStandardEngineContextFactory() default true;
+    boolean useStandardEngineContextFactory() default false;
 
 }
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/resourceresolver/DelegatingResourceResolver.java b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/resourceresolver/DelegatingResourceResolver.java
new file mode 100644
index 0000000..25b1c4d
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/resourceresolver/DelegatingResourceResolver.java
@@ -0,0 +1,231 @@
+/*
+ * 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.internal.resourceresolver;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DelegatingResourceResolver implements ResourceResolver {
+
+    private final ResourceResolver resourceResolver;
+
+    private final Logger logger = LoggerFactory.getLogger(DelegatingResourceResolver.class);
+
+    public DelegatingResourceResolver(final ResourceResolver resourceResolver) {
+        this.resourceResolver = resourceResolver;
+    }
+
+    void closeInternal() {
+        resourceResolver.close();
+    }
+
+    @Override
+    @Nonnull
+    public Resource resolve(@Nonnull HttpServletRequest httpServletRequest,@Nonnull String s) {
+        return resourceResolver.resolve(httpServletRequest, s);
+    }
+
+    @Override
+    @Nonnull
+    public Resource resolve(@Nonnull String s) {
+        return resourceResolver.resolve(s);
+    }
+
+    @Override
+    @Deprecated
+    @Nonnull
+    public Resource resolve(@Nonnull HttpServletRequest httpServletRequest) {
+        return resourceResolver.resolve(httpServletRequest);
+    }
+
+    @Override
+    @Nonnull
+    public String map(@Nonnull String s) {
+        return resourceResolver.map(s);
+    }
+
+    @Override
+    @CheckForNull
+    public String map(@Nonnull HttpServletRequest httpServletRequest, @Nonnull String s) {
+        return resourceResolver.map(httpServletRequest, s);
+    }
+
+    @Override
+    @CheckForNull
+    public Resource getResource(@Nonnull String s) {
+        return resourceResolver.getResource(s);
+    }
+
+    @Override
+    @CheckForNull
+    public Resource getResource(Resource resource, @Nonnull String s) {
+        return resourceResolver.getResource(resource, s);
+    }
+
+    @Override
+    @Nonnull
+    public String[] getSearchPath() {
+        return resourceResolver.getSearchPath();
+    }
+
+    @Override
+    @Nonnull
+    public Iterator<Resource> listChildren(@Nonnull Resource resource) {
+        return resourceResolver.listChildren(resource);
+    }
+
+    @Override
+    @CheckForNull
+    public Resource getParent(@Nonnull Resource resource) {
+        return resourceResolver.getParent(resource);
+    }
+
+    @Override
+    @Nonnull
+    public Iterable<Resource> getChildren(@Nonnull Resource resource) {
+        return resourceResolver.getChildren(resource);
+    }
+
+    @Override
+    @Nonnull
+    public Iterator<Resource> findResources(@Nonnull String s, String s1) {
+        return resourceResolver.findResources(s, s1);
+    }
+
+    @Override
+    @Nonnull
+    public Iterator<Map<String, Object>> queryResources(@Nonnull String s, String s1) {
+        return resourceResolver.queryResources(s, s1);
+    }
+
+    @Override
+    public boolean hasChildren(@Nonnull Resource resource) {
+        return resourceResolver.hasChildren(resource);
+    }
+
+    @Override
+    @Nonnull
+    public ResourceResolver clone(Map<String, Object> map) throws LoginException {
+        return resourceResolver.clone(map);
+    }
+
+    @Override
+    public boolean isLive() {
+        return resourceResolver.isLive();
+    }
+
+    @Override
+    public void close() {
+        // do not close
+    }
+
+    @Override
+    @CheckForNull
+    public String getUserID() {
+        return resourceResolver.getUserID();
+    }
+
+    @Override
+    @Nonnull
+    public Iterator<String> getAttributeNames() {
+        return resourceResolver.getAttributeNames();
+    }
+
+    @Override
+    @CheckForNull
+    public Object getAttribute(@Nonnull String s) {
+        return resourceResolver.getAttribute(s);
+    }
+
+    @Override
+    public void delete(@Nonnull Resource resource) throws PersistenceException {
+        resourceResolver.delete(resource);
+    }
+
+    @Override
+    @Nonnull
+    public Resource create(@Nonnull Resource resource, @Nonnull String s, Map<String, Object> map) throws PersistenceException {
+        return resourceResolver.create(resource, s, map);
+    }
+
+    @Override
+    public void revert() {
+        resourceResolver.revert();
+    }
+
+    @Override
+    public void commit() throws PersistenceException {
+        resourceResolver.commit();
+    }
+
+    @Override
+    public boolean hasChanges() {
+        return resourceResolver.hasChanges();
+    }
+
+    @Override
+    @CheckForNull
+    public String getParentResourceType(Resource resource) {
+        return resourceResolver.getParentResourceType(resource);
+    }
+
+    @Override
+    @CheckForNull
+    public String getParentResourceType(String s) {
+        return resourceResolver.getParentResourceType(s);
+    }
+
+    @Override
+    public boolean isResourceType(Resource resource, String s) {
+        return resourceResolver.isResourceType(resource, s);
+    }
+
+    @Override
+    public void refresh() {
+        resourceResolver.refresh();
+    }
+
+    @Override
+    public Resource copy(String s, String s1) throws PersistenceException {
+        return resourceResolver.copy(s, s1);
+    }
+
+    @Override
+    public Resource move(String s, String s1) throws PersistenceException {
+        return resourceResolver.move(s, s1);
+    }
+
+    @Override
+    @CheckForNull
+    public <AdapterType> AdapterType adaptTo(@Nonnull Class<AdapterType> aClass) {
+        return resourceResolver.adaptTo(aClass);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/thymeleaf/internal/resourceresolver/RequestScopedResourceResolverProvider.java b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/resourceresolver/RequestScopedResourceResolverProvider.java
new file mode 100644
index 0000000..4da19ad
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/thymeleaf/internal/resourceresolver/RequestScopedResourceResolverProvider.java
@@ -0,0 +1,90 @@
+/*
+ * 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.internal.resourceresolver;
+
+import org.apache.sling.api.request.SlingRequestEvent;
+import org.apache.sling.api.request.SlingRequestEvent.EventType;
+import org.apache.sling.api.request.SlingRequestListener;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.serviceusermapping.ServiceUserMapped;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+    service = {
+        RequestScopedResourceResolverProvider.class,
+        SlingRequestListener.class
+    },
+    immediate = true
+)
+public class RequestScopedResourceResolverProvider implements SlingRequestListener {
+
+    private final ThreadLocal<DelegatingResourceResolver> threadLocal = new ThreadLocal<>();
+
+    @Reference(
+        policy = ReferencePolicy.DYNAMIC,
+        policyOption = ReferencePolicyOption.GREEDY
+    )
+    private volatile ResourceResolverFactory resourceResolverFactory;
+
+    @Reference(
+        cardinality = ReferenceCardinality.MANDATORY,
+        policy = ReferencePolicy.DYNAMIC,
+        policyOption = ReferencePolicyOption.GREEDY
+    )
+    private volatile ServiceUserMapped serviceUserMapped;
+
+    private final Logger logger = LoggerFactory.getLogger(RequestScopedResourceResolverProvider.class);
+
+    public ResourceResolver getResourceResolver() {
+        DelegatingResourceResolver resourceResolver = threadLocal.get();
+        if (resourceResolver == null) {
+            try {
+                logger.debug("getting service resource resolver for thread {}", Thread.currentThread().getName());
+                final ResourceResolver delegate = resourceResolverFactory.getServiceResourceResolver(null);
+                resourceResolver = new DelegatingResourceResolver(delegate);
+                logger.debug("setting service resource resolver {} for thread {}", resourceResolver, Thread.currentThread().getName());
+                threadLocal.set(resourceResolver);
+            } catch (Exception e) {
+                logger.error(e.getMessage(), e);
+            }
+        }
+        return resourceResolver;
+    }
+
+    @Override
+    public void onEvent(final SlingRequestEvent slingRequestEvent) {
+        if (EventType.EVENT_DESTROY.equals(slingRequestEvent.getType())) {
+            final DelegatingResourceResolver resourceResolver = threadLocal.get();
+            logger.debug("removing service resource resolver {} for thread {}", resourceResolver, Thread.currentThread().getName());
+            threadLocal.remove();
+            if (resourceResolver != null) {
+                logger.debug("closing resource resolver {} for thread {}", resourceResolver, Thread.currentThread().getName());
+                resourceResolver.closeInternal();
+            }
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/scripting/thymeleaf/it/tests/ThymeleafTestSupport.java b/src/test/java/org/apache/sling/scripting/thymeleaf/it/tests/ThymeleafTestSupport.java
index 693b94e..ef295be 100644
--- a/src/test/java/org/apache/sling/scripting/thymeleaf/it/tests/ThymeleafTestSupport.java
+++ b/src/test/java/org/apache/sling/scripting/thymeleaf/it/tests/ThymeleafTestSupport.java
@@ -25,12 +25,15 @@ import org.apache.sling.api.servlets.ServletResolver;
 import org.apache.sling.auth.core.AuthenticationSupport;
 import org.apache.sling.engine.SlingRequestProcessor;
 import org.apache.sling.scripting.thymeleaf.it.app.Activator;
+import org.apache.sling.testing.paxexam.SlingOptions;
+import org.apache.sling.testing.paxexam.SlingVersionResolver;
 import org.apache.sling.testing.paxexam.TestSupport;
 import org.ops4j.pax.exam.Configuration;
 import org.ops4j.pax.exam.Option;
 import org.ops4j.pax.exam.ProbeBuilder;
 import org.ops4j.pax.exam.TestProbeBuilder;
 import org.ops4j.pax.exam.util.Filter;
+import org.ops4j.pax.exam.util.PathUtils;
 import org.osgi.framework.Constants;
 import org.osgi.service.http.HttpService;
 import org.thymeleaf.ITemplateEngine;
@@ -43,6 +46,9 @@ import static org.apache.sling.testing.paxexam.SlingOptions.slingScriptingJsp;
 import static org.ops4j.pax.exam.CoreOptions.composite;
 import static org.ops4j.pax.exam.CoreOptions.junitBundles;
 import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
 
 public abstract class ThymeleafTestSupport extends TestSupport {
 
@@ -67,6 +73,14 @@ public abstract class ThymeleafTestSupport extends TestSupport {
 
     @Configuration
     public Option[] configuration() {
+        // SlingOptions.versionResolver.setVersionFromProject(SlingVersionResolver.SLING_GROUP_ID, "org.apache.sling.scripting.api");
+        // SlingOptions.versionResolver.setVersionFromProject(SlingVersionResolver.SLING_GROUP_ID, "org.apache.sling.scripting.core");
+        SlingOptions.versionResolver.setVersion(SlingVersionResolver.SLING_GROUP_ID, "org.apache.sling.api", "2.14.3-SNAPSHOT");
+        SlingOptions.versionResolver.setVersion(SlingVersionResolver.SLING_GROUP_ID, "org.apache.sling.jcr.base", "2.4.1-SNAPSHOT");
+        SlingOptions.versionResolver.setVersion(SlingVersionResolver.SLING_GROUP_ID, "org.apache.sling.jcr.oak.server", "1.1.1-SNAPSHOT");
+        SlingOptions.versionResolver.setVersion(SlingVersionResolver.SLING_GROUP_ID, "org.apache.sling.jcr.repoinit", "1.0.3-SNAPSHOT");
+        SlingOptions.versionResolver.setVersion(SlingVersionResolver.SLING_GROUP_ID, "org.apache.sling.jcr.resource", "2.8.1-SNAPSHOT");
+        SlingOptions.versionResolver.setVersion(SlingVersionResolver.SLING_GROUP_ID, "org.apache.sling.resourceresolver", "1.4.19-SNAPSHOT");
         return new Option[]{
             baseConfiguration(),
             launchpad(),
@@ -76,7 +90,8 @@ public abstract class ThymeleafTestSupport extends TestSupport {
             // testing
             mavenBundle().groupId("org.jsoup").artifactId("jsoup").versionAsInProject(),
             mavenBundle().groupId("org.apache.servicemix.bundles").artifactId("org.apache.servicemix.bundles.hamcrest").versionAsInProject(),
-            junitBundles()
+            junitBundles(),
+            logging()
         };
     }
 
@@ -96,12 +111,33 @@ public abstract class ThymeleafTestSupport extends TestSupport {
     protected Option launchpad() {
         final int httpPort = findFreePort();
         final String workingDirectory = workingDirectory();
+        final String repoinit = String.format("raw:file:%s/src/test/resources/repoinit.txt", PathUtils.getBaseDir());
         return composite(
             slingLaunchpadOakTar(workingDirectory, httpPort),
             slingExtensionI18n(),
             slingExtensionModels(),
             slingScripting(),
-            slingScriptingJsp()
+            slingScriptingJsp(),
+            newConfiguration("org.apache.sling.jcr.repoinit.impl.RepositoryInitializer")
+                .put("references", new String[]{repoinit})
+                .asOption(),
+            factoryConfiguration("org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended")
+                .put("user.mapping", "org.apache.sling.scripting.thymeleaf=sling-scripting")
+                .asOption(),
+            newConfiguration("org.apache.sling.jcr.base.internal.LoginAdminWhitelistImpl")
+                .put("whitelist.regexp", "org.apache.sling.*")
+                .asOption()
+        );
+    }
+
+    protected Option logging() {
+        final String filename = String.format("file:%s/src/test/resources/logback.xml", PathUtils.getBaseDir());
+        return composite(
+            systemProperty("logback.configurationFile").value(filename),
+            mavenBundle().groupId("org.slf4j").artifactId("slf4j-api").version("1.7.21"),
+            mavenBundle().groupId("org.slf4j").artifactId("jcl-over-slf4j").version("1.7.21"),
+            mavenBundle().groupId("ch.qos.logback").artifactId("logback-core").version("1.1.7"),
+            mavenBundle().groupId("ch.qos.logback").artifactId("logback-classic").version("1.1.7")
         );
     }
 
diff --git a/src/test/resources/exam.properties b/src/test/resources/exam.properties
new file mode 100644
index 0000000..c98a668
--- /dev/null
+++ b/src/test/resources/exam.properties
@@ -0,0 +1,19 @@
+#
+#  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.
+#
+pax.exam.logging=none
diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml
new file mode 100644
index 0000000..d46a4ae
--- /dev/null
+++ b/src/test/resources/logback.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<configuration>
+  <appender name="file" class="ch.qos.logback.core.FileAppender">
+    <file>target/testing.log</file>
+    <encoder>
+      <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg%n</pattern>
+    </encoder>
+  </appender>
+  <root level="debug">
+    <appender-ref ref="file"/>
+  </root>
+</configuration>
diff --git a/src/test/resources/repoinit.txt b/src/test/resources/repoinit.txt
new file mode 100644
index 0000000..fc4c50c
--- /dev/null
+++ b/src/test/resources/repoinit.txt
@@ -0,0 +1,34 @@
+################################################################################
+#
+#    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.
+#
+################################################################################
+
+create service user sling-scripting
+
+create path (sling:Folder) /apps
+create path (sling:Folder) /libs
+
+set ACL for sling-scripting
+  allow jcr:read on /apps
+  allow jcr:read on /libs
+end
+
+create path (sling:OrderedFolder) /content
+
+set ACL for everyone
+  allow jcr:read on /content
+end

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