You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2020/03/27 11:29:31 UTC

[sling-org-apache-sling-scripting-bundle-tracker] branch master updated: SLING-9255 - Allow servlet resolution based on path

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2aba608  SLING-9255 - Allow servlet resolution based on path
2aba608 is described below

commit 2aba608350614b09eca65829af0ffbc077fabd73
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Fri Mar 27 12:28:12 2020 +0100

    SLING-9255 - Allow servlet resolution based on path
    
    * servlets are also registered by path to allow calls like
    data-sly-include, sling:call
    * refactored the way inheritance information is generated (n-level
    inheritance)
---
 .../bundle/tracker/BundledRenderUnit.java          |  40 ++----
 .../internal/AbstractBundledRenderUnit.java        |  51 ++++---
 .../tracker/internal/BundledScriptFinder.java      | 152 +++++++--------------
 .../tracker/internal/BundledScriptServlet.java     |  66 +++------
 .../tracker/internal/BundledScriptTracker.java     | 125 ++++++++---------
 .../bundle/tracker/internal/Executable.java        |  33 +++++
 .../bundle/tracker/internal/PrecompiledScript.java |  41 +++---
 .../tracker/internal/ResourceTypeCapability.java   |  74 ++++++----
 .../scripting/bundle/tracker/internal/Script.java  |  14 +-
 .../tracker/internal/ScriptContextProvider.java    |  43 +++++-
 .../bundle/tracker/internal/TypeProvider.java      |  37 +++--
 11 files changed, 345 insertions(+), 331 deletions(-)

diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/BundledRenderUnit.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/BundledRenderUnit.java
index 179a6e1..eda9d58 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/BundledRenderUnit.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/BundledRenderUnit.java
@@ -18,10 +18,6 @@
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 package org.apache.sling.scripting.bundle.tracker;
 
-import javax.script.ScriptContext;
-import javax.script.ScriptEngine;
-import javax.script.ScriptException;
-
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.osgi.annotation.versioning.ProviderType;
@@ -65,14 +61,6 @@ public interface BundledRenderUnit {
     @NotNull String getName();
 
     /**
-     * Returns an instance of the {@link ScriptEngine} that can execute the wrapped script or precompiled script, if the latter needs a
-     * specific runtime.
-     *
-     * @return an instance of the script's or precompiled script's associated {@link ScriptEngine}
-     */
-    @NotNull ScriptEngine getScriptEngine();
-
-    /**
      * Returns the {@link Bundle} in which the script or precompiled script is packaged. This method can be useful for getting an
      * instance of the bundle's classloader, when needed to load dependencies at run time. To do so the following code example can help:
      *
@@ -84,31 +72,23 @@ public interface BundledRenderUnit {
     @NotNull Bundle getBundle();
 
     /**
-     * Provided a {@link ScriptContext}, this method will execute / evaluate the wrapped script or precompiled script.
-     *
-     * @param context the {@link ScriptContext}
-     * @throws ScriptException if the execution leads to an error
-     */
-    void eval(@NotNull ScriptContext context) throws ScriptException;
-
-    /**
      * Retrieves an OSGi runtime dependency of the wrapped script identified by the passed {@code className} parameter.
      *
-     * @param className     the fully qualified class name
-     * @param <ServiceType> the expected service type
-     * @return an instance of the {@link ServiceType} or {@code null}
+     * @param className the fully qualified class name
+     * @param <T>       the expected service type
+     * @return an instance of the {@link T} or {@code null}
      */
-    @Nullable <ServiceType> ServiceType getService(@NotNull String className);
+    @Nullable <T> T getService(@NotNull String className);
 
     /**
      * Retrieves multiple instances of an OSGi runtime dependency of the wrapped script identified by the passed {@code className}
      * parameter, filtered according to the passed {@code filter}.
      *
-     * @param className     the fully qualified class name
-     * @param filter        a filter expression or {@code null} if all the instances should be returned; for more details about the {@code
-     *                      filter}'s syntax check {@link org.osgi.framework.BundleContext#getServiceReferences(String, String)}
-     * @param <ServiceType> the expected service type
-     * @return an instance of the {@link ServiceType} or {@code null}
+     * @param className the fully qualified class name
+     * @param filter    a filter expression or {@code null} if all the instances should be returned; for more details about the {@code
+     *                  filter}'s syntax check {@link org.osgi.framework.BundleContext#getServiceReferences(String, String)}
+     * @param <T>       the expected service type
+     * @return an instance of the {@link T} or {@code null}
      */
-    @Nullable <ServiceType> ServiceType[] getServices(@NotNull String className, @Nullable String filter);
+    @Nullable <T> T[] getServices(@NotNull String className, @Nullable String filter);
 }
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/AbstractBundledRenderUnit.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/AbstractBundledRenderUnit.java
index 192934d..b82a4f8 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/AbstractBundledRenderUnit.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/AbstractBundledRenderUnit.java
@@ -40,11 +40,16 @@ abstract class AbstractBundledRenderUnit implements Executable {
 
     private final Bundle bundle;
     private final BundleContext bundleContext;
-    private List<ServiceReference> references;
+    private final String path;
+    private final String scriptEngineName;
+    private List<ServiceReference<?>> references;
     private Map<String, Object> services;
 
-    AbstractBundledRenderUnit(@NotNull Bundle bundle) {
+
+    AbstractBundledRenderUnit(@NotNull Bundle bundle, @NotNull String path, @NotNull String scriptEngineName) {
         this.bundle = bundle;
+        this.path = path;
+        this.scriptEngineName = scriptEngineName;
         bundleContext = bundle.getBundleContext();
     }
 
@@ -55,15 +60,25 @@ abstract class AbstractBundledRenderUnit implements Executable {
     }
 
     @Override
+    public @NotNull String getPath() {
+        return path;
+    }
+
+    @Override
+    public @NotNull String getScriptEngineName() {
+        return scriptEngineName;
+    }
+
+    @Override
     @Nullable
     @SuppressWarnings("unchecked")
-    public <ServiceType> ServiceType getService(@NotNull String className) {
+    public <T> T getService(@NotNull String className) {
         LOG.debug("Attempting to load class {} as an OSGi service.", className);
-        ServiceType result = (this.services == null ? null : (ServiceType) this.services.get(className));
+        T result = (this.services == null ? null : (T) this.services.get(className));
         if (result == null) {
-            final ServiceReference ref = this.bundleContext.getServiceReference(className);
+            final ServiceReference<?> ref = this.bundleContext.getServiceReference(className);
             if (ref != null) {
-                result = (ServiceType) this.bundleContext.getService(ref);
+                result = (T) this.bundleContext.getService(ref);
                 if (result != null) {
                     if (this.services == null) {
                         this.services = new HashMap<>();
@@ -83,21 +98,21 @@ abstract class AbstractBundledRenderUnit implements Executable {
     @Override
     @Nullable
     @SuppressWarnings("unchecked")
-    public <ServiceType> ServiceType[] getServices(@NotNull String className, @Nullable String filter) {
-        ServiceType[] result = null;
+    public <T> T[] getServices(@NotNull String className, @Nullable String filter) {
+        T[] result = null;
         try {
-            final ServiceReference[] refs = this.bundleContext.getServiceReferences(className, filter);
+            final ServiceReference<?>[] refs = this.bundleContext.getServiceReferences(className, filter);
 
             if (refs != null) {
                 // sort by service ranking (lowest first) (see ServiceReference#compareTo(Object))
-                List<ServiceReference> references = Arrays.asList(refs);
-                Collections.sort(references);
+                List<ServiceReference<?>> localReferences = Arrays.asList(refs);
+                Collections.sort(localReferences);
                 // get the highest ranking first
-                Collections.reverse(references);
+                Collections.reverse(localReferences);
 
-                final List<ServiceType> objects = new ArrayList<>();
-                for (ServiceReference reference : references) {
-                    final ServiceType service = (ServiceType) this.bundleContext.getService(reference);
+                final List<T> objects = new ArrayList<>();
+                for (ServiceReference<?> reference : localReferences) {
+                    final T service = (T) this.bundleContext.getService(reference);
                     if (service != null) {
                         if (this.references == null) {
                             this.references = new ArrayList<>();
@@ -106,8 +121,8 @@ abstract class AbstractBundledRenderUnit implements Executable {
                         objects.add(service);
                     }
                 }
-                if (objects.size() > 0) {
-                    ServiceType[] srv = (ServiceType[]) Array.newInstance(bundle.loadClass(className), objects.size());
+                if (!objects.isEmpty()) {
+                    T[] srv = (T[]) Array.newInstance(bundle.loadClass(className), objects.size());
                     result = objects.toArray(srv);
                 }
             }
@@ -120,7 +135,7 @@ abstract class AbstractBundledRenderUnit implements Executable {
     @Override
     public void releaseDependencies() {
         if (references != null) {
-            for (ServiceReference reference : this.references) {
+            for (ServiceReference<?> reference : this.references) {
                 bundleContext.ungetService(reference);
             }
             references.clear();
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptFinder.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptFinder.java
index bbe9973..42f15e7 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptFinder.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptFinder.java
@@ -22,88 +22,68 @@ import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineFactory;
-import javax.script.ScriptEngineManager;
-
 import org.apache.commons.lang3.StringUtils;
-import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.commons.compiler.source.JavaEscapeHelper;
 import org.apache.sling.scripting.bundle.tracker.ResourceType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.osgi.framework.Bundle;
-import org.osgi.framework.wiring.BundleCapability;
-import org.osgi.framework.wiring.BundleWiring;
 import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 @Component(
         service = BundledScriptFinder.class
 )
 public class BundledScriptFinder {
 
-    private static final Logger LOGGER = LoggerFactory.getLogger(BundledScriptFinder.class);
     private static final String NS_JAVAX_SCRIPT_CAPABILITY = "javax.script";
     private static final String SLASH = "/";
     private static final String DOT = ".";
 
-    @Reference
-    private ScriptEngineManager scriptEngineManager;
-
-    Executable getScript(SlingHttpServletRequest request, LinkedHashSet<TypeProvider> typeProviders, boolean precompiledScripts) {
-        List<String> scriptMatches;
-        for (TypeProvider provider : typeProviders) {
-            scriptMatches = buildScriptMatches(request, provider.getResourceTypes());
-            String scriptEngineName = getScriptEngineName(request, provider);
-            if (StringUtils.isNotEmpty(scriptEngineName)) {
-                ScriptEngine scriptEngine = scriptEngineManager.getEngineByName(scriptEngineName);
-                if (scriptEngine != null) {
-                    for (String match : scriptMatches) {
-                        URL bundledScriptURL;
-                        List<String> scriptEngineExtensions = getScriptEngineExtensions(scriptEngineName);
-                        for (String scriptEngineExtension : scriptEngineExtensions) {
-                            if (precompiledScripts) {
-                                String className = JavaEscapeHelper.makeJavaPackage(match + DOT + scriptEngineExtension);
-                                try {
-                                    Class clazz = provider.getBundle().loadClass(className);
-                                    return new PrecompiledScript(provider.getBundle(), scriptEngine,
-                                            clazz.getDeclaredConstructor().newInstance());
-                                } catch (ClassNotFoundException e) {
-                                    // do nothing here
-                                } catch (Exception e) {
-                                    throw new IllegalStateException("Cannot correctly instantiate class " + className + ".");
-                                }
-                            } else {
-                                bundledScriptURL =
-                                        provider.getBundle()
-                                                .getEntry(NS_JAVAX_SCRIPT_CAPABILITY + (match.startsWith("/") ? "" : SLASH) + match + DOT + scriptEngineExtension);
-                                if (bundledScriptURL != null) {
-                                    return new Script(provider.getBundle(), bundledScriptURL, scriptEngine);
-                                }
-                            }
-                        }
+    Executable getScript(Set<TypeProvider> providers) {
+        for (TypeProvider provider : providers) {
+            ResourceTypeCapability capability = provider.getResourceTypeCapability();
+            for (String match : buildScriptMatches(capability.getResourceTypes(),
+                    capability.getSelectors().toArray(new String[0]), capability.getMethod(), capability.getExtension())) {
+                String scriptExtension = capability.getScriptExtension();
+                String scriptEngineName = capability.getScriptEngineName();
+                if (StringUtils.isNotEmpty(scriptExtension) && StringUtils.isNotEmpty(scriptEngineName)) {
+                    Executable executable =
+                            getExecutable(provider.getBundle(), provider.isPrecompiled(), match, scriptEngineName, scriptExtension);
+                    if (executable != null) {
+                        return executable;
                     }
-                } else {
-                    LOGGER.error("Cannot find a script engine for short name {}.", scriptEngineName);
                 }
-            } else {
-                LOGGER.error("Cannot find a script engine name for {}.", provider);
             }
         }
         return null;
     }
 
-    private List<String> buildScriptMatches(SlingHttpServletRequest request, Set<ResourceType> resourceTypes) {
+    @Nullable
+    private Executable getExecutable(@NotNull Bundle bundle, boolean precompiled, @NotNull String match,
+                                     @NotNull String scriptEngineName, @NotNull String scriptExtension) {
+        String path = match + DOT + scriptExtension;
+        if (precompiled) {
+            String className = JavaEscapeHelper.makeJavaPackage(path);
+            try {
+                Class<?> clazz = bundle.loadClass(className);
+                return new PrecompiledScript(bundle, path, clazz, scriptEngineName);
+            } catch (ClassNotFoundException ignored) {
+                // do nothing here
+            }
+        } else {
+            URL bundledScriptURL = bundle.getEntry(NS_JAVAX_SCRIPT_CAPABILITY + (match.startsWith("/") ? "" : SLASH) + path);
+            if (bundledScriptURL != null) {
+                return new Script(bundle, path, bundledScriptURL, scriptEngineName);
+            }
+        }
+        return null;
+    }
+
+    private List<String> buildScriptMatches(Set<ResourceType> resourceTypes, String[] selectors, String method, String extension) {
         List<String> matches = new ArrayList<>();
-        String method = request.getMethod();
-        String extension = request.getRequestPathInfo().getExtension();
-        String[] selectors = request.getRequestPathInfo().getSelectors();
         for (ResourceType resourceType : resourceTypes) {
             if (selectors.length > 0) {
                 for (int i = selectors.length - 1; i >= 0; i--) {
@@ -113,10 +93,14 @@ public class BundledScriptFinder {
                                             SLASH) +
                                     String.join(SLASH, Arrays.copyOf(selectors, i + 1));
                     if (StringUtils.isNotEmpty(extension)) {
-                        matches.add(base + DOT + extension + DOT + method);
+                        if (StringUtils.isNotEmpty(method)) {
+                            matches.add(base + DOT + extension + DOT + method);
+                        }
                         matches.add(base + DOT + extension);
                     }
-                    matches.add(base + DOT + method);
+                    if (StringUtils.isNotEmpty(method)) {
+                        matches.add(base + DOT + method);
+                    }
                     matches.add(base);
                 }
             }
@@ -124,56 +108,22 @@ public class BundledScriptFinder {
                     (StringUtils.isNotEmpty(resourceType.getVersion()) ? SLASH + resourceType.getVersion() : StringUtils.EMPTY);
 
             if (StringUtils.isNotEmpty(extension)) {
-                matches.add(base + SLASH + resourceType.getResourceLabel() + DOT + extension + DOT + method);
+                if (StringUtils.isNotEmpty(method)) {
+                    matches.add(base + SLASH + resourceType.getResourceLabel() + DOT + extension + DOT + method);
+                }
                 matches.add(base + SLASH + resourceType.getResourceLabel() + DOT + extension);
             }
-            matches.add(base + SLASH + resourceType.getResourceLabel() + DOT + method);
+            if (StringUtils.isNotEmpty(method)) {
+                matches.add(base + SLASH + resourceType.getResourceLabel() + DOT + method);
+            }
             matches.add(base + SLASH + resourceType.getResourceLabel());
-            matches.add(base + SLASH + method);
+            if (StringUtils.isNotEmpty(method)) {
+                matches.add(base + SLASH + method);
+            }
             if (StringUtils.isNotEmpty(extension)) {
                 matches.add(base + SLASH + extension);
             }
         }
         return Collections.unmodifiableList(matches);
     }
-
-    private String getScriptEngineName(SlingHttpServletRequest request, TypeProvider typeProvider) {
-        String scriptEngineName = null;
-        Bundle bundle = typeProvider.getBundle();
-        BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
-        List<BundleCapability> capabilities = bundleWiring.getCapabilities(BundledScriptTracker.NS_SLING_RESOURCE_TYPE);
-        String[] selectors = request.getRequestPathInfo().getSelectors();
-        String requestExtension = request.getRequestPathInfo().getExtension();
-        String requestMethod = request.getMethod();
-        for (BundleCapability capability : capabilities) {
-            ResourceTypeCapability resourceTypeCapability = ResourceTypeCapability.fromBundleCapability(capability);
-            for (ResourceType resourceType : typeProvider.getResourceTypes()) {
-                if (
-                        resourceTypeCapability.getResourceTypes().contains(resourceType) &&
-                        Arrays.equals(selectors, resourceTypeCapability.getSelectors().toArray()) &&
-                        ((resourceTypeCapability.getExtensions().isEmpty() && "html".equals(requestExtension)) ||
-                                resourceTypeCapability.getExtensions().contains(requestExtension)) &&
-                        ((resourceTypeCapability.getMethods().isEmpty() &&
-                                ("GET".equals(requestMethod) || "HEAD".equals(requestMethod))) ||
-                                resourceTypeCapability.getMethods().contains(requestMethod))
-                ) {
-                    scriptEngineName = resourceTypeCapability.getScriptEngineName();
-                    if (scriptEngineName != null) {
-                        break;
-                    }
-                }
-            }
-        }
-        return scriptEngineName;
-    }
-
-    private List<String> getScriptEngineExtensions(String scriptEngineName) {
-        for (ScriptEngineFactory factory : scriptEngineManager.getEngineFactories()) {
-            Set<String> factoryNames = new HashSet<>(factory.getNames());
-            if (factoryNames.contains(scriptEngineName)) {
-                return factory.getExtensions();
-            }
-        }
-        return Collections.emptyList();
-    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptServlet.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptServlet.java
index 0516ece..e053a58 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptServlet.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptServlet.java
@@ -21,46 +21,33 @@ package org.apache.sling.scripting.bundle.tracker.internal;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.LinkedHashSet;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 import java.util.stream.Collectors;
 
-import javax.script.Bindings;
-import javax.script.ScriptContext;
 import javax.script.ScriptException;
 import javax.servlet.GenericServlet;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 
-import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.api.SlingConstants;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.SlingHttpServletResponse;
-import org.apache.sling.api.request.RequestPathInfo;
-import org.apache.sling.api.scripting.ScriptEvaluationException;
-import org.apache.sling.api.scripting.SlingBindings;
-import org.apache.sling.scripting.core.ScriptHelper;
+import org.jetbrains.annotations.NotNull;
 
 class BundledScriptServlet extends GenericServlet {
 
 
-    private final BundledScriptFinder m_bundledScriptFinder;
-    private final ScriptContextProvider m_scriptContextProvider;
-    private final LinkedHashSet<TypeProvider> m_wiredTypeProviders;
-    private final boolean m_precompiledScripts;
+    private final ScriptContextProvider scriptContextProvider;
+    private final LinkedHashSet<TypeProvider> wiredTypeProviders;
+    private final Executable executable;
 
-    private ConcurrentMap<String, Optional<Executable>> scriptsMap = new ConcurrentHashMap<>();
 
-
-
-    BundledScriptServlet(BundledScriptFinder bundledScriptFinder, ScriptContextProvider scriptContextProvider,
-                         LinkedHashSet<TypeProvider> wiredTypeProviders, boolean precompiledScripts) {
-        m_bundledScriptFinder = bundledScriptFinder;
-        m_scriptContextProvider = scriptContextProvider;
-        m_wiredTypeProviders = wiredTypeProviders;
-        m_precompiledScripts = precompiledScripts;
+    BundledScriptServlet(@NotNull ScriptContextProvider scriptContextProvider,
+                         @NotNull LinkedHashSet<TypeProvider> wiredTypeProviders,
+                         @NotNull Executable executable) {
+        this.scriptContextProvider = scriptContextProvider;
+        this.wiredTypeProviders = wiredTypeProviders;
+        this.executable = executable;
     }
 
     @Override
@@ -79,40 +66,21 @@ class BundledScriptServlet extends GenericServlet {
                 }
             }
 
-            String scriptsMapKey = getScriptsMapKey(request);
-            Executable executable = scriptsMap.computeIfAbsent(scriptsMapKey,
-                key -> Optional.ofNullable(m_bundledScriptFinder.getScript(request, m_wiredTypeProviders, m_precompiledScripts))
-            ).orElseThrow(() -> new ServletException("Unable to locate a " + (m_precompiledScripts ? "class" : "script") + " for rendering."));
-
             RequestWrapper requestWrapper = new RequestWrapper(request,
-                    m_wiredTypeProviders.stream().map(TypeProvider::getResourceTypes).flatMap(Collection::stream).collect(Collectors.toSet()));
-            ScriptContext scriptContext = m_scriptContextProvider.prepareScriptContext(requestWrapper, response, executable);
+                    wiredTypeProviders.stream().map(typeProvider -> typeProvider.getResourceTypeCapability().getResourceTypes()
+            ).flatMap(Collection::stream).collect(Collectors.toSet()));
+            ScriptContextProvider.ExecutableContext executableContext = scriptContextProvider
+                    .prepareScriptContext(requestWrapper, response, executable);
             try {
-                executable.eval(scriptContext);
+                executableContext.eval();
             } catch (ScriptException se) {
                 Throwable cause = (se.getCause() == null) ? se : se.getCause();
-                throw new ScriptEvaluationException(executable.getName(), se.getMessage(), cause);
+                throw new ServletException(String.format("Failed executing script %s: %s", executable.getName(), se.getMessage()), cause);
             } finally {
-                Bindings engineBindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
-                if (engineBindings != null && engineBindings.containsKey(SlingBindings.SLING)) {
-                    Object scriptHelper = engineBindings.get(SlingBindings.SLING);
-                    if (scriptHelper instanceof ScriptHelper) {
-                        ((ScriptHelper) scriptHelper).cleanup();
-                    }
-                }
-                executable.releaseDependencies();
+                executableContext.clean();
             }
         } else {
             throw new ServletException("Not a Sling HTTP request/response");
         }
     }
-
-    private String getScriptsMapKey(SlingHttpServletRequest request) {
-        RequestPathInfo requestPathInfo = request.getRequestPathInfo();
-        String selectorString = requestPathInfo.getSelectorString();
-        String requestExtension = requestPathInfo.getExtension();
-        return request.getMethod() + ":" + request.getResource().getResourceType() +
-                (StringUtils.isNotEmpty(selectorString) ? ":" + selectorString : "") +
-                (StringUtils.isNotEmpty(requestExtension) ? ":" + requestExtension : "");
-    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptTracker.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptTracker.java
index 540efde..9006823 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptTracker.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/BundledScriptTracker.java
@@ -50,6 +50,7 @@ import org.apache.sling.api.request.RequestDispatcherOptions;
 import org.apache.sling.api.servlets.ServletResolverConstants;
 import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.apache.sling.scripting.bundle.tracker.ResourceType;
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.bundle.Capability;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
@@ -88,6 +89,7 @@ public class BundledScriptTracker implements BundleTrackerCustomizer<List<Servic
     private static final String REGISTERING_BUNDLE = "org.apache.sling.scripting.bundle.tracker.internal.BundledScriptTracker.registering_bundle";
     static final String AT_VERSION = "version";
     static final String AT_SCRIPT_ENGINE = "scriptEngine";
+    static final String AT_SCRIPT_EXTENSION = "scriptExtension";
     static final String AT_EXTENDS = "extends";
 
     @Reference
@@ -121,7 +123,6 @@ public class BundledScriptTracker implements BundleTrackerCustomizer<List<Servic
             LOGGER.debug("Inspecting bundle {} for {} capability.", bundle.getSymbolicName(), NS_SLING_RESOURCE_TYPE);
             List<BundleCapability> capabilities = bundleWiring.getCapabilities(NS_SLING_RESOURCE_TYPE);
             if (!capabilities.isEmpty()) {
-                boolean precompiled = Boolean.parseBoolean(bundle.getHeaders().get("Sling-ResourceType-Precompiled"));
                 List<ServiceRegistration<Servlet>> serviceRegistrations = capabilities.stream().flatMap(cap ->
                 {
                     Hashtable<String, Object> properties = new Hashtable<>();
@@ -135,55 +136,48 @@ public class BundledScriptTracker implements BundleTrackerCustomizer<List<Servic
                     }
                     properties.put(ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES, resourceTypesRegistrationValue);
 
-                    Set<String> extensions = new HashSet<>(resourceTypeCapability.getExtensions());
-                    if (extensions.isEmpty()) {
-                        extensions.add("html");
+                    String extension = resourceTypeCapability.getExtension();
+                    if (StringUtils.isEmpty(extension)) {
+                        extension = "html";
                     }
-                    properties.put(ServletResolverConstants.SLING_SERVLET_EXTENSIONS, extensions.toArray());
+                    properties.put(ServletResolverConstants.SLING_SERVLET_EXTENSIONS, extension);
 
                     if (!resourceTypeCapability.getSelectors().isEmpty()) {
                         properties.put(ServletResolverConstants.SLING_SERVLET_SELECTORS, resourceTypeCapability.getSelectors().toArray());
                     }
 
-                    if (!resourceTypeCapability.getMethods().isEmpty()) {
-                        properties.put(ServletResolverConstants.SLING_SERVLET_METHODS, resourceTypeCapability.getMethods().toArray());
+                    if (StringUtils.isNotEmpty(resourceTypeCapability.getMethod())) {
+                        properties.put(ServletResolverConstants.SLING_SERVLET_METHODS, resourceTypeCapability.getMethod());
                     }
 
                     List<ServiceRegistration<Servlet>> regs = new ArrayList<>();
                     LinkedHashSet<TypeProvider> wiredProviders = new LinkedHashSet<>();
-                    wiredProviders.add(new TypeProvider(resourceTypeCapability.getResourceTypes(), bundle));
+                    wiredProviders.add(new TypeProvider(resourceTypeCapability, bundle));
                     String extendedResourceTypeString = resourceTypeCapability.getExtendedResourceType();
                     if (StringUtils.isNotEmpty(extendedResourceTypeString)) {
-                        Bundle providingBundle = null;
-                        ResourceType extendedResourceType = null;
-                        ResourceTypeCapability extendedCapability = null;
-                        for (BundleWire wire : bundleWiring.getRequiredWires(NS_SLING_RESOURCE_TYPE)) {
-                            ResourceTypeCapability wiredCapability =
-                                    ResourceTypeCapability.fromBundleCapability(wire.getCapability());
-                            for (ResourceType resourceType : wiredCapability.getResourceTypes()) {
-                                if (extendedResourceTypeString.equals(resourceType.getType()) && wiredCapability.getSelectors().isEmpty()) {
-                                    providingBundle = wire.getProvider().getBundle();
-                                    extendedCapability = wiredCapability;
-                                    extendedResourceType = resourceType;
-                                    LOGGER.debug("Bundle {} extends resource type {} through {}.", bundle.getSymbolicName(),
-                                            extendedResourceType.toString(), resourceTypesRegistrationValue);
-                                    break;
+                        collectProvidersChain(wiredProviders, bundleWiring, extendedResourceTypeString);
+                        wiredProviders.stream().filter(typeProvider -> typeProvider.getResourceTypeCapability().getResourceTypes().stream().anyMatch(resourceType -> resourceType.getType().equals(extendedResourceTypeString))).findFirst().ifPresent(typeProvider -> {
+                            for (ResourceType type : typeProvider.getResourceTypeCapability().getResourceTypes()) {
+                                if (type.getType().equals(extendedResourceTypeString)) {
+                                    properties.put(ServletResolverConstants.SLING_SERVLET_RESOURCE_SUPER_TYPE, type.toString());
                                 }
                             }
-                        }
-                        if (providingBundle != null) {
-                            wiredProviders.add(new TypeProvider(extendedCapability.getResourceTypes(), providingBundle));
-                            properties.put(ServletResolverConstants.SLING_SERVLET_RESOURCE_SUPER_TYPE, extendedResourceType.toString());
-                        }
+                        });
+                    }
+                    Executable executable = bundledScriptFinder.getScript(wiredProviders);
+                    if (executable != null) {
+                        properties.put(ServletResolverConstants.SLING_SERVLET_PATHS, executable.getPath());
+                        regs.add(
+                                bundle.getBundleContext().registerService(
+                                        Servlet.class,
+                                        new BundledScriptServlet(scriptContextProvider, wiredProviders, executable),
+                                        properties
+                                )
+                        );
+                    } else {
+                        LOGGER.error(String.format("Unable to locate an executable for capability %s.", cap));
                     }
-                    populateWiredProviders(wiredProviders);
-                    regs.add(
-                        bundle.getBundleContext().registerService(
-                                Servlet.class,
-                                new BundledScriptServlet(bundledScriptFinder, scriptContextProvider, wiredProviders, precompiled),
-                                properties
-                        )
-                    );
+
                     return regs.stream();
                 }).collect(Collectors.toList());
                 refreshDispatcher(serviceRegistrations);
@@ -196,34 +190,6 @@ public class BundledScriptTracker implements BundleTrackerCustomizer<List<Servic
         }
     }
 
-
-    private void populateWiredProviders(LinkedHashSet<TypeProvider> initialProviders) {
-        Set<ResourceType> initialResourceTypes = new HashSet<>();
-        Set<Bundle> bundles = new HashSet<>(initialProviders.size());
-        initialProviders.forEach(typeProvider -> {
-            initialResourceTypes.addAll(typeProvider.getResourceTypes());
-            bundles.add(typeProvider.getBundle());
-        });
-        for (Bundle bundle : bundles) {
-            BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
-            bundleWiring.getRequiredWires(BundledScriptTracker.NS_SLING_RESOURCE_TYPE).forEach(
-                    bundleWire ->
-                    {
-                        ResourceTypeCapability wiredCapability = ResourceTypeCapability.fromBundleCapability(bundleWire.getCapability());
-                        Set<ResourceType> wiredResourceTypes = new HashSet<>();
-                        for (ResourceType capabilityRT : wiredCapability.getResourceTypes()) {
-                            if (!initialResourceTypes.contains(capabilityRT)) {
-                                wiredResourceTypes.add(capabilityRT);
-                            }
-                        }
-                        if (!wiredResourceTypes.isEmpty()) {
-                            initialProviders.add(new TypeProvider(wiredResourceTypes, bundleWire.getProvider().getBundle()));
-                        }
-                    }
-            );
-        }
-    }
-
     private void refreshDispatcher(List<ServiceRegistration<Servlet>> regs) {
         Map<Set<String>, ServiceRegistration<Servlet>> dispatchers = new HashMap<>();
         Stream.concat(m_tracker.getTracked().values().stream(), Stream.of(regs)).flatMap(List::stream)
@@ -317,8 +283,7 @@ public class BundledScriptTracker implements BundleTrackerCustomizer<List<Servic
 
             Optional<ServiceRegistration<Servlet>> target = m_tracker.getTracked().values().stream().flatMap(List::stream)
                     .filter(
-                            ((Predicate<ServiceRegistration<Servlet>>) reg -> reg.getReference().getBundle().equals(m_context.getBundle()))
-                                    .negate()
+                            reg -> !reg.getReference().getBundle().equals(m_context.getBundle())
                     )
                     .filter(reg -> getResourceTypeVersion(reg.getReference()) != null)
                     .filter(reg ->
@@ -399,4 +364,34 @@ public class BundledScriptTracker implements BundleTrackerCustomizer<List<Servic
         }
         return resourceTypes;
     }
+
+    private void collectProvidersChain(@NotNull Set<TypeProvider> providers, @NotNull BundleWiring wiring,
+                                       @NotNull String extendedResourceType) {
+        for (BundleWire wire : wiring.getRequiredWires(NS_SLING_RESOURCE_TYPE)) {
+            ResourceTypeCapability wiredCapability = ResourceTypeCapability.fromBundleCapability(wire.getCapability());
+            if (wiredCapability.getSelectors().isEmpty()) {
+                for (ResourceType resourceType : wiredCapability.getResourceTypes()) {
+                    if (extendedResourceType.equals(resourceType.getType())) {
+                        Bundle providingBundle = wire.getProvider().getBundle();
+                        providers.add(new TypeProvider(wiredCapability, providingBundle));
+                        for (BundleWire providedWire : wire.getProvider().getWiring().getRequiredWires(NS_SLING_RESOURCE_TYPE)) {
+                            ResourceTypeCapability resourceTypeCapability =
+                                    ResourceTypeCapability.fromBundleCapability(providedWire.getCapability());
+                            String capabilityExtends = resourceTypeCapability.getExtendedResourceType();
+                            if (resourceTypeCapability.getSelectors().isEmpty() && StringUtils.isNotEmpty(capabilityExtends)) {
+                                for (ResourceType providedResourceType : resourceTypeCapability.getResourceTypes()) {
+                                    if (providedResourceType.getType().equals(extendedResourceType)) {
+                                        collectProvidersChain(providers, providedWire.getProvider()
+                                                .getBundle().adapt(BundleWiring.class), capabilityExtends);
+                                    }
+                                }
+                            } else {
+                                providers.add(new TypeProvider(resourceTypeCapability, providedWire.getProvider().getBundle()));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/Executable.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/Executable.java
index e9a1b79..8797103 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/Executable.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/Executable.java
@@ -18,10 +18,43 @@
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 package org.apache.sling.scripting.bundle.tracker.internal;
 
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+
 import org.apache.sling.scripting.bundle.tracker.BundledRenderUnit;
+import org.jetbrains.annotations.NotNull;
 
 interface Executable extends BundledRenderUnit {
 
+    /**
+     * Releases all acquired dependencies which were retrieved through {@link #getService(String)} or {@link #getServices(String, String)}.
+     */
     void releaseDependencies();
 
+    /**
+     * Returns the path of this executable in the resource type hierarchy. The path can be relative to the search paths or absolute.
+     *
+     * @return the path of this executable in the resource type hierarchy
+     */
+    @NotNull
+    String getPath();
+
+    /**
+     * Returns the short name of the {@link ScriptEngine} with which {@code this Executable} can be evaluated.
+     *
+     * @return the short name of the script engine
+     * @see #eval(ScriptEngine, ScriptContext)
+     */
+    @NotNull String getScriptEngineName();
+
+    /**
+     * Provided a {@link ScriptContext}, this method will execute / evaluate the wrapped script or precompiled script.
+     *
+     * @param scriptEngine a suitable script engine; see {@link #getScriptEngineName()} in order to see what {@link ScriptEngine}
+     *                     implementation is expected
+     * @param context      the {@link ScriptContext}
+     * @throws ScriptException if the execution leads to an error
+     */
+    void eval(@NotNull ScriptEngine scriptEngine, @NotNull ScriptContext context) throws ScriptException;
 }
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/PrecompiledScript.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/PrecompiledScript.java
index caec607..565e3ce 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/PrecompiledScript.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/PrecompiledScript.java
@@ -25,43 +25,48 @@ import javax.script.ScriptEngine;
 import javax.script.ScriptException;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.sling.api.scripting.SlingScriptConstants;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 import org.osgi.framework.Bundle;
 
 public class PrecompiledScript extends AbstractBundledRenderUnit {
 
     private static final StringReader EMPTY_READER = new StringReader(StringUtils.EMPTY);
+    private final Class<?> clazz;
+    private volatile Object instance;
 
-    private final ScriptEngine scriptEngine;
-    private final Object precompiledScript;
-
-    PrecompiledScript(@NotNull Bundle bundle, @NotNull ScriptEngine scriptEngine, @NotNull Object precompiledScript) {
-        super(bundle);
-        this.scriptEngine = scriptEngine;
-        this.precompiledScript = precompiledScript;
+    PrecompiledScript(@NotNull Bundle bundle, @NotNull String path, @NotNull Class<?> clazz, @NotNull String scriptEngineName) {
+        super(bundle, path, scriptEngineName);
+        this.clazz = clazz;
     }
 
     @Override
     @NotNull
     public String getName() {
-        return precompiledScript.getClass().getName();
-    }
-
-    @Override
-    @NotNull
-    public ScriptEngine getScriptEngine() {
-        return scriptEngine;
+        return clazz.getName();
     }
 
     @Override
-    public void eval(@NotNull ScriptContext context) throws ScriptException {
+    public void eval(@NotNull ScriptEngine scriptEngine, @NotNull ScriptContext context) throws ScriptException {
         scriptEngine.eval(EMPTY_READER, context);
     }
 
     @Override
     public @NotNull Object getUnit() {
-        return precompiledScript;
+        Object localInstance = instance;
+        if (localInstance == null) {
+            synchronized (this) {
+                localInstance = instance;
+                if (localInstance == null) {
+                    try {
+                        localInstance = clazz.getDeclaredConstructor().newInstance();
+                        instance = localInstance;
+                    } catch (Exception e) {
+                        throw new IllegalStateException("Cannot instantiate class " + clazz.getName(), e);
+                    }
+                }
+            }
+        }
+        return localInstance;
     }
+
 }
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceTypeCapability.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceTypeCapability.java
index 9648dc1..756750c 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceTypeCapability.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceTypeCapability.java
@@ -21,7 +21,9 @@ package org.apache.sling.scripting.bundle.tracker.internal;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 import org.apache.sling.api.servlets.ServletResolverConstants;
@@ -35,21 +37,24 @@ import org.osgi.framework.wiring.BundleCapability;
 class ResourceTypeCapability {
 
     private final Set<ResourceType> resourceTypes;
-    private final Set<String> selectors;
-    private final Set<String> extensions;
-    private final Set<String> methods;
+    private final List<String> selectors;
+    private final String extension;
+    private final String method;
     private final String extendedResourceType;
     private final String scriptEngineName;
+    private final String scriptExtension;
 
-    private ResourceTypeCapability(@NotNull Set<ResourceType> resourceTypes, @NotNull Set<String> selectors,
-                                   @NotNull Set<String> extensions, @NotNull Set<String> methods,
-                                   @Nullable String extendedResourceType, @Nullable String scriptEngineName) {
+    private ResourceTypeCapability(@NotNull Set<ResourceType> resourceTypes, @NotNull List<String> selectors,
+                                   @Nullable String extension, @Nullable String method,
+                                   @Nullable String extendedResourceType, @Nullable String scriptEngineName,
+                                   @Nullable String scriptExtension) {
         this.resourceTypes = resourceTypes;
         this.selectors = selectors;
-        this.extensions = extensions;
-        this.methods = methods;
+        this.extension = extension;
+        this.method = method;
         this.extendedResourceType = extendedResourceType;
         this.scriptEngineName = scriptEngineName;
+        this.scriptExtension = scriptExtension;
     }
 
     @NotNull
@@ -58,13 +63,13 @@ class ResourceTypeCapability {
     }
 
     @NotNull
-    Set<String> getSelectors() {
-        return Collections.unmodifiableSet(selectors);
+    List<String> getSelectors() {
+        return Collections.unmodifiableList(selectors);
     }
 
-    @NotNull
-    Set<String> getExtensions() {
-        return Collections.unmodifiableSet(extensions);
+    @Nullable
+    String getExtension() {
+        return extension;
     }
 
     @Nullable
@@ -72,9 +77,9 @@ class ResourceTypeCapability {
         return extendedResourceType;
     }
 
-    @NotNull
-    Set<String> getMethods() {
-        return Collections.unmodifiableSet(methods);
+    @Nullable
+    String getMethod() {
+        return method;
     }
 
     @Nullable
@@ -82,6 +87,31 @@ class ResourceTypeCapability {
         return scriptEngineName;
     }
 
+    @Nullable
+    String getScriptExtension() {
+        return scriptExtension;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(resourceTypes, selectors, extension, method, extendedResourceType, scriptEngineName, scriptExtension);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof ResourceTypeCapability) {
+            ResourceTypeCapability other = (ResourceTypeCapability) obj;
+            return Objects.equals(resourceTypes, other.resourceTypes) && Objects.equals(selectors, other.selectors) &&
+                    Objects.equals(extension, other.extension) && Objects.equals(method, other.method) &&
+                    Objects.equals(extendedResourceType, other.extendedResourceType) &&
+                    Objects.equals(scriptEngineName, other.scriptEngineName) && Objects.equals(scriptExtension, other.scriptExtension);
+        }
+        return false;
+    }
+
     static ResourceTypeCapability fromBundleCapability(@NotNull BundleCapability capability) {
         Map<String, Object> attributes = capability.getAttributes();
         Set<ResourceType> resourceTypes = new HashSet<>();
@@ -97,14 +127,12 @@ class ResourceTypeCapability {
         }
         return new ResourceTypeCapability(
                 resourceTypes,
-                new HashSet<>(Arrays.asList(
-                        PropertiesUtil.toStringArray(attributes.get(BundledScriptTracker.AT_SLING_SELECTORS), new String[0]))),
-                new HashSet<>(Arrays.asList(PropertiesUtil.toStringArray(attributes.get(BundledScriptTracker.AT_SLING_EXTENSIONS),
-                        new String[0]))),
-                new HashSet<>(Arrays.asList(
-                        PropertiesUtil.toStringArray(attributes.get(ServletResolverConstants.SLING_SERVLET_METHODS), new String[0]))),
+                Arrays.asList(PropertiesUtil.toStringArray(attributes.get(BundledScriptTracker.AT_SLING_SELECTORS), new String[0])),
+                (String) attributes.get(BundledScriptTracker.AT_SLING_EXTENSIONS),
+                (String) attributes.get(ServletResolverConstants.SLING_SERVLET_METHODS),
                 (String) attributes.get(BundledScriptTracker.AT_EXTENDS),
-                (String) attributes.get(BundledScriptTracker.AT_SCRIPT_ENGINE)
+                (String) attributes.get(BundledScriptTracker.AT_SCRIPT_ENGINE),
+                (String) attributes.get(BundledScriptTracker.AT_SCRIPT_EXTENSION)
         );
     }
 }
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/Script.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/Script.java
index b752f49..ed20fbc 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/Script.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/Script.java
@@ -39,17 +39,15 @@ import org.osgi.framework.Bundle;
 class Script extends AbstractBundledRenderUnit {
 
     private final URL url;
-    private final ScriptEngine scriptEngine;
     private String sourceCode;
     private CompiledScript compiledScript = null;
     private Lock compilationLock = new ReentrantLock();
     private Lock readLock = new ReentrantLock();
 
 
-    Script(Bundle bundle, URL url, ScriptEngine scriptEngine) {
-        super(bundle);
+    Script(@NotNull Bundle bundle, @NotNull String path, @NotNull URL url, @NotNull String scriptEngineName) {
+        super(bundle, path, scriptEngineName);
         this.url = url;
-        this.scriptEngine = scriptEngine;
     }
 
     private String getSourceCode() throws IOException {
@@ -72,14 +70,8 @@ class Script extends AbstractBundledRenderUnit {
         return url.getPath();
     }
 
-    @NotNull
-    @Override
-    public ScriptEngine getScriptEngine() {
-        return scriptEngine;
-    }
-
     @Override
-    public void eval(@NotNull ScriptContext context) throws ScriptException {
+    public void eval(@NotNull ScriptEngine scriptEngine, @NotNull ScriptContext context) throws ScriptException {
         try {
             if (scriptEngine instanceof Compilable && compiledScript == null) {
                 compilationLock.lock();
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ScriptContextProvider.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ScriptContextProvider.java
index 3ab0ffd..5607669 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ScriptContextProvider.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ScriptContextProvider.java
@@ -29,6 +29,8 @@ import java.util.Set;
 import javax.script.Bindings;
 import javax.script.ScriptContext;
 import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
 import javax.script.SimpleBindings;
 
 import org.apache.sling.api.SlingHttpServletRequest;
@@ -68,10 +70,18 @@ public class ScriptContextProvider {
     private BindingsValuesProvidersByContext bvpTracker;
 
     @Reference
+    private ScriptEngineManager scriptEngineManager;
+
+    @Reference
     private ScriptingResourceResolverProvider scriptingResourceResolverProvider;
 
-    ScriptContext prepareScriptContext(SlingHttpServletRequest request, SlingHttpServletResponse response, Executable executable)
+    ExecutableContext prepareScriptContext(SlingHttpServletRequest request, SlingHttpServletResponse response, Executable executable)
             throws IOException {
+        ScriptEngine scriptEngine = scriptEngineManager.getEngineByName(executable.getScriptEngineName());
+        if (scriptEngine == null) {
+            throw new IllegalStateException(String.format("Cannot find a script engine with name %s for executable %s.",
+                    executable.getScriptEngineName(), executable.getPath()));
+        }
         // prepare the SlingBindings
         Bindings bindings = new SimpleBindings();
         bindings.put("properties", request.getResource().getValueMap());
@@ -89,7 +99,7 @@ public class ScriptContextProvider {
         bindings.put(ScriptEngine.FILENAME.replaceAll("\\.", "_"), executable.getName());
 
         ProtectedBindings protectedBindings = new ProtectedBindings(bindings, PROTECTED_BINDINGS);
-        for (BindingsValuesProvider bindingsValuesProvider : bvpTracker.getBindingsValuesProviders(executable.getScriptEngine().getFactory(),
+        for (BindingsValuesProvider bindingsValuesProvider : bvpTracker.getBindingsValuesProviders(scriptEngine.getFactory(),
                 BindingsValuesProvider.DEFAULT_CONTEXT)) {
             bindingsValuesProvider.addBindings(protectedBindings);
         }
@@ -103,7 +113,34 @@ public class ScriptContextProvider {
         scriptContext.setWriter(response.getWriter());
         scriptContext.setErrorWriter(new LogWriter(scriptLogger));
         scriptContext.setReader(request.getReader());
-        return scriptContext;
+        return new ExecutableContext(scriptContext, executable, scriptEngine);
+    }
+
+    static class ExecutableContext {
+        private final ScriptContext scriptContext;
+        private final Executable executable;
+        private final ScriptEngine scriptEngine;
+
+        private ExecutableContext(ScriptContext scriptContext, Executable executable, ScriptEngine scriptEngine) {
+            this.scriptContext = scriptContext;
+            this.executable = executable;
+            this.scriptEngine = scriptEngine;
+        }
+
+        void eval() throws ScriptException {
+            executable.eval(scriptEngine, scriptContext);
+        }
+
+        void clean() {
+            Bindings engineBindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
+            if (engineBindings != null && engineBindings.containsKey(SlingBindings.SLING)) {
+                Object scriptHelper = engineBindings.get(SlingBindings.SLING);
+                if (scriptHelper instanceof ScriptHelper) {
+                    ((ScriptHelper) scriptHelper).cleanup();
+                }
+            }
+            executable.releaseDependencies();
+        }
     }
 
 
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/TypeProvider.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/TypeProvider.java
index c8cda53..daa4bdf 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/TypeProvider.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/TypeProvider.java
@@ -19,9 +19,7 @@
 package org.apache.sling.scripting.bundle.tracker.internal;
 
 import java.util.Objects;
-import java.util.Set;
 
-import org.apache.sling.scripting.bundle.tracker.ResourceType;
 import org.osgi.framework.Bundle;
 
 /**
@@ -29,27 +27,29 @@ import org.osgi.framework.Bundle;
  */
 public class TypeProvider {
 
-    private final Set<ResourceType> resourceTypes;
+    private final ResourceTypeCapability resourceTypeCapability;
     private final Bundle bundle;
+    private final boolean precompiled;
 
     /**
      * Builds a {@code TypeProvider}.
      *
-     * @param resourceTypes   the resource type
+     * @param resourceTypeCapability  the resource type capability
      * @param bundle the bundle that provides the resource type
      */
-    TypeProvider(Set<ResourceType> resourceTypes, Bundle bundle) {
-        this.resourceTypes = resourceTypes;
+    TypeProvider(ResourceTypeCapability resourceTypeCapability, Bundle bundle) {
+        this.resourceTypeCapability = resourceTypeCapability;
         this.bundle = bundle;
+        precompiled = Boolean.parseBoolean(bundle.getHeaders().get("Sling-ResourceType-Precompiled"));
     }
 
     /**
-     * Returns the resource type.
+     * Returns the resource type capabilities.
      *
-     * @return the resource type
+     * @return the resource type capabilities
      */
-    Set<ResourceType> getResourceTypes() {
-        return resourceTypes;
+    ResourceTypeCapability getResourceTypeCapability() {
+        return resourceTypeCapability;
     }
 
     /**
@@ -61,9 +61,18 @@ public class TypeProvider {
         return bundle;
     }
 
+    /**
+     * Returns {@code true} if the bundle provides precompiled scripts.
+     *
+     * @return {@code true} if the bundle provides precompiled scripts, {@code false} otherwise
+     */
+    public boolean isPrecompiled() {
+        return precompiled;
+    }
+
     @Override
     public int hashCode() {
-        return Objects.hash(bundle, resourceTypes);
+        return Objects.hash(bundle, resourceTypeCapability, precompiled);
     }
 
     @Override
@@ -73,13 +82,15 @@ public class TypeProvider {
         }
         if (obj instanceof TypeProvider) {
             TypeProvider other = (TypeProvider) obj;
-            return Objects.equals(bundle, other.bundle) && Objects.equals(resourceTypes, other.resourceTypes);
+            return Objects.equals(bundle, other.bundle) && Objects.equals(resourceTypeCapability, other.resourceTypeCapability) &&
+                    Objects.equals(precompiled, other.precompiled);
         }
         return false;
     }
 
     @Override
     public String toString() {
-        return String.format("TypeProvider{ resourceTypes=%s; bundle=%s }", resourceTypes, bundle.getSymbolicName());
+        return String.format("TypeProvider{ resourceTypeCapability=%s; bundle=%s; precompiled=%s }", resourceTypeCapability,
+                bundle.getSymbolicName(), precompiled);
     }
 }