You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ol...@apache.org on 2019/09/15 14:12:27 UTC

[sling-org-apache-sling-scripting-core] 02/02: SLING-4330 Select script engines in SlingScriptAdapterFactory via mapping

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

olli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-core.git

commit 09e48f47691dbb1825e74c44f1f2bb6df9c9eb78
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Sun Sep 15 16:11:36 2019 +0200

    SLING-4330 Select script engines in SlingScriptAdapterFactory via mapping
    
    Add SlingScriptEnginePicker and use picker in SlingScriptAdapterFactory when more than one engine is available per script extension
---
 .../core/impl/SlingScriptAdapterFactory.java       |   9 +-
 .../core/impl/SlingScriptEnginePicker.java         | 128 ++++++++++++++++++++
 .../sling/scripting/core/it/HtmlScriptingIT.java   | 129 +++++++++++++++++++++
 src/test/resources/apps/htl.json                   |   6 +
 src/test/resources/apps/htl/page/page.html         |  27 +++++
 src/test/resources/apps/thymeleaf.json             |   6 +
 src/test/resources/apps/thymeleaf/page/page.html   |  27 +++++
 src/test/resources/content/scripting.json          |  14 +++
 8 files changed, 343 insertions(+), 3 deletions(-)

diff --git a/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java b/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java
index bb6ceff..4f64f5d 100644
--- a/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java
+++ b/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java
@@ -71,6 +71,9 @@ public class SlingScriptAdapterFactory implements AdapterFactory, MimeTypeProvid
     @Reference
     private SlingScriptEngineManager scriptEngineManager;
 
+    @Reference
+    private volatile SlingScriptEnginePicker scriptEnginePicker;
+
     /**
      * The BindingsValuesProviderTracker
      */
@@ -88,10 +91,10 @@ public class SlingScriptAdapterFactory implements AdapterFactory, MimeTypeProvid
 
         final Resource resource = (Resource) adaptable;
         final String path = resource.getPath();
-        final String ext = path.substring(path.lastIndexOf('.') + 1);
+        final String extension = path.substring(path.lastIndexOf('.') + 1);
 
-        final List<ScriptEngine> engines = scriptEngineManager.getEnginesByExtension(ext);
-        final ScriptEngine engine = engines.size() > 0 ? engines.get(0) : null;
+        final List<ScriptEngine> engines = scriptEngineManager.getEnginesByExtension(extension);
+        final ScriptEngine engine = scriptEnginePicker.pickScriptEngine(engines, resource, extension);
         if (engine != null) {
             final Collection<BindingsValuesProvider> bindingsValuesProviders = bindingsValuesProviderTracker.getBindingsValuesProviders(engine.getFactory(), BINDINGS_CONTEXT);
             // unchecked cast
diff --git a/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptEnginePicker.java b/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptEnginePicker.java
new file mode 100644
index 0000000..666189d
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptEnginePicker.java
@@ -0,0 +1,128 @@
+/*
+ * 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.core.impl;
+
+import java.util.List;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+
+import org.apache.sling.api.resource.Resource;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+    service = {SlingScriptEnginePicker.class},
+    property = {
+        Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
+        Constants.SERVICE_DESCRIPTION + "=Apache Sling Scripting SlingScriptEnginePicker"
+    }
+)
+public class SlingScriptEnginePicker {
+
+    /**
+     * The property contains the required language mapping
+     *
+     * "sling:scripting": [
+     * "html=The HTL Templating Language:1.4"
+     * ]
+     * "sling:scripting": [
+     * "html=Thymeleaf:3.0"
+     * ]
+     */
+    private static String SLING_SCRIPTING = "sling:scripting";
+
+    private final Logger logger = LoggerFactory.getLogger(SlingScriptEnginePicker.class);
+
+    public SlingScriptEnginePicker() {
+    }
+
+    @Nullable ScriptEngine pickScriptEngine(@NotNull final List<ScriptEngine> scriptEngines, @NotNull Resource resource, @NotNull String extension) {
+        final String scriptingMapping = findScriptingMapping(resource, extension);
+        logger.debug("scripting mapping: {}", scriptingMapping);
+        if (scriptingMapping == null || scriptingMapping.isEmpty()) {
+            return scriptEngines.size() > 0 ? scriptEngines.get(0) : null;
+        }
+
+        final Conditions conditions = parseScriptingMapping(scriptingMapping);
+        logger.debug("scripting conditions: {}", conditions);
+        for (final ScriptEngine scriptEngine : scriptEngines) {
+            final ScriptEngineFactory scriptEngineFactory = scriptEngine.getFactory();
+            if (conditions.matches(scriptEngineFactory.getLanguageName())) {
+                logger.debug("script engine {} found for conditions {}", scriptEngineFactory.getEngineName(), conditions);
+                return scriptEngine;
+            }
+        }
+        return null;
+    }
+
+    private String findScriptingMapping(@NotNull final Resource resource, @NotNull final String extension) {
+        final String[] mappings = resource.getValueMap().get(SLING_SCRIPTING, String[].class);
+        if (mappings != null) {
+            final String start = String.format("%s=", extension);
+            for (final String mapping : mappings) {
+                if (mapping.startsWith(start)) {
+                    return mapping.substring(start.length());
+                }
+            }
+            return resource.getParent() != null ? findScriptingMapping(resource.getParent(), extension) : null;
+        } else if (resource.getParent() != null) {
+            return findScriptingMapping(resource.getParent(), extension);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * TODO Take language version, engine name and engine version into account
+     */
+    private Conditions parseScriptingMapping(@NotNull final String scriptingMapping) {
+        final String[] values = scriptingMapping.split(":");
+        final String languageName = values[0];
+        final String languageVersion = values.length > 1 ? values[1] : null;
+        // values[2] -> engine name
+        // values[3] -> engine version
+        return new Conditions(languageName, languageVersion);
+    }
+
+    private class Conditions {
+
+        final String languageName;
+
+        final String languageVersion;
+
+        Conditions(@NotNull final String languageName, @Nullable final String languageVersion) {
+            this.languageName = languageName;
+            this.languageVersion = languageVersion;
+        }
+
+        boolean matches(final String languageName) {
+            return this.languageName.equalsIgnoreCase(languageName);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s, %s", languageName, languageVersion);
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/scripting/core/it/HtmlScriptingIT.java b/src/test/java/org/apache/sling/scripting/core/it/HtmlScriptingIT.java
new file mode 100644
index 0000000..9714fb2
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/core/it/HtmlScriptingIT.java
@@ -0,0 +1,129 @@
+/*
+ * 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.core.it;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+import javax.script.ScriptEngineFactory;
+
+import org.apache.sling.api.adapter.AdapterFactory;
+import org.apache.sling.api.servlets.ServletResolver;
+import org.apache.sling.resource.presence.ResourcePresence;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+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.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.ops4j.pax.exam.util.Filter;
+import org.osgi.service.http.HttpService;
+
+import static org.apache.sling.testing.paxexam.SlingOptions.slingQuickstartOakTar;
+import static org.apache.sling.testing.paxexam.SlingOptions.slingScriptingSightly;
+import static org.apache.sling.testing.paxexam.SlingOptions.slingScriptingThymeleaf;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class HtmlScriptingIT extends ScriptingCoreTestSupport {
+
+    @Inject
+    protected ServletResolver servletResolver;
+
+    // SlingScriptAdapterFactory
+    @Inject
+    @Filter(value = "(adapters=org.apache.sling.api.scripting.SlingScript)")
+    protected AdapterFactory adapterFactory;
+
+    @Inject
+    protected HttpService httpService;
+
+    @Inject
+    @Filter(value = "(names=htl)")
+    protected ScriptEngineFactory htlScriptEngineFactory;
+
+    @Inject
+    @Filter(value = "(names=thymeleaf)")
+    protected ScriptEngineFactory thymeleafScriptEngineFactory;
+
+    @Inject
+    @Filter(value = "(path=/apps/htl)")
+    private ResourcePresence htl;
+
+    @Inject
+    @Filter(value = "(path=/apps/thymeleaf)")
+    private ResourcePresence thymeleaf;
+
+    @Inject
+    @Filter(value = "(path=/content/scripting)")
+    private ResourcePresence scripting;
+
+    @Configuration
+    public Option[] configuration() {
+        final String workingDirectory = workingDirectory();
+        return remove(options(
+            super.baseConfiguration(),
+            slingScriptingSightly(),
+            slingScriptingThymeleaf(),
+            slingQuickstartOakTar(workingDirectory, httpPort),
+            factoryConfiguration("org.apache.sling.resource.presence.internal.ResourcePresenter")
+                .put("path", "/apps/htl")
+                .asOption(),
+            factoryConfiguration("org.apache.sling.resource.presence.internal.ResourcePresenter")
+                .put("path", "/apps/thymeleaf")
+                .asOption(),
+            factoryConfiguration("org.apache.sling.resource.presence.internal.ResourcePresenter")
+                .put("path", "/content/scripting")
+                .asOption()
+        ), scriptingCore);
+    }
+
+    @ProbeBuilder
+    public TestProbeBuilder probeConfiguration(final TestProbeBuilder testProbeBuilder) {
+        testProbeBuilder.setHeader("Sling-Initial-Content", String.join(",",
+            "apps;path:=/apps;overwrite:=true;uninstall:=true",
+            "content;path:=/content;overwrite:=true;uninstall:=true"
+        ));
+        return testProbeBuilder;
+    }
+
+    @Test
+    public void testHtlTitle() throws IOException {
+        final String url = String.format("http://localhost:%s/scripting/htl.html", httpPort());
+        final Document document = Jsoup.connect(url).get();
+        assertThat(document.title(), is("Sling Scripting HTL"));
+    }
+
+    @Test
+    public void testThymeleafTitle() throws IOException {
+        final String url = String.format("http://localhost:%s/scripting/thymeleaf.html", httpPort());
+        final Document document = Jsoup.connect(url).get();
+        assertThat(document.title(), is("Sling Scripting Thymeleaf"));
+    }
+
+}
diff --git a/src/test/resources/apps/htl.json b/src/test/resources/apps/htl.json
new file mode 100644
index 0000000..4e0d286
--- /dev/null
+++ b/src/test/resources/apps/htl.json
@@ -0,0 +1,6 @@
+{
+  "sling:scripting": [
+    "html=The HTL Templating Language:1.4",
+    "js=ECMAScript:partial ECMAScript 2015 support"
+  ]
+}
diff --git a/src/test/resources/apps/htl/page/page.html b/src/test/resources/apps/htl/page/page.html
new file mode 100644
index 0000000..b3a05b3
--- /dev/null
+++ b/src/test/resources/apps/htl/page/page.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!--
+    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.
+-->
+<html lang="en">
+<head>
+  <meta charset="UTF-8"/>
+  <title>${properties.title}</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/src/test/resources/apps/thymeleaf.json b/src/test/resources/apps/thymeleaf.json
new file mode 100644
index 0000000..cc55018
--- /dev/null
+++ b/src/test/resources/apps/thymeleaf.json
@@ -0,0 +1,6 @@
+{
+  "sling:scripting": [
+    "html=Thymeleaf:3.0",
+    "js=ECMAScript:partial ECMAScript 2015 support"
+  ]
+}
diff --git a/src/test/resources/apps/thymeleaf/page/page.html b/src/test/resources/apps/thymeleaf/page/page.html
new file mode 100644
index 0000000..48be9ac
--- /dev/null
+++ b/src/test/resources/apps/thymeleaf/page/page.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!--
+    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.
+-->
+<html lang="en">
+<head>
+  <meta charset="UTF-8"/>
+  <title data-th-text="${resource.getValueMap().get('title')}">HTML Title</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/src/test/resources/content/scripting.json b/src/test/resources/content/scripting.json
new file mode 100644
index 0000000..41a2e98
--- /dev/null
+++ b/src/test/resources/content/scripting.json
@@ -0,0 +1,14 @@
+{
+    "jcr:primaryType": "nt:unstructured",
+    "title": "Apache Sling Scripting",
+    "htl": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "htl/page",
+        "title": "Sling Scripting HTL"
+    },
+    "thymeleaf": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "thymeleaf/page",
+        "title": "Sling Scripting Thymeleaf"
+    }
+}