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 <[email protected]> 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 "[email protected]" <[email protected]>.
