SLING-9406: Add bundled script support to the servlets resolver
diff --git a/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/BundledRenderUnit.java b/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/BundledRenderUnit.java
new file mode 100644
index 0000000..5cd95b5
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/BundledRenderUnit.java
@@ -0,0 +1,122 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.servlets.resolver.bundle.tracker;
+
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.annotation.versioning.ProviderType;
+import org.osgi.framework.Bundle;
+
+/**
+ * <p>
+ * A {@code BundledRenderUnit} represents a pre-packaged script or precompiled script that will be executed in order to render a
+ * {@link org.apache.sling.api.SlingHttpServletRequest}.
+ * </p>
+ * <p>
+ * If the current {@link org.apache.sling.api.SlingHttpServletRequest} is served by a {@code BundledRenderUnit}, the
+ * {@code org.apache.sling.scripting.bundle.tracker} will set the {@code BundledRenderUnit} in the {@link javax.script.Bindings} map associated to the request,
+ * under the {@link #VARIABLE} key.
+ * </p>
+ */
+@ProviderType
+public interface BundledRenderUnit {
+
+    /**
+     * The variable available in the {@link javax.script.Bindings} associated to a {@link org.apache.sling.api.SlingHttpServletRequest}
+     * if that request is served by a {@code BundledRenderUnit}.
+     */
+    String VARIABLE = BundledRenderUnit.class.getName();
+
+    /**
+     * In case this {@code BundledRenderUnit} wraps a precompiled script, this method will return an instance of that object.
+     *
+     * @return a precompiled unit, if {@code this} unit wraps a precompiled script; {@code null} otherwise
+     */
+    @Nullable
+    default Object getUnit() {
+        return null;
+    }
+
+    /**
+     * Returns the name of {@code this BundledRenderUnit}. This can be the name of the wrapped script or precompiled script.
+     *
+     * @return the name {@code this BundledRenderUnit}
+     */
+    @NotNull String getName();
+
+    /**
+     * 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:
+     *
+     * <pre>
+     * Bundle bundle = bundledRenderUnit.getBundle();
+     * Classloader bundleClassloader = bundle.adapt(BundleWiring.class).getClassLoader();
+     * </pre>
+     */
+    @NotNull Bundle getBundle();
+
+    /**
+     * Returns the {@code Set} of {@link TypeProvider}s which are related to this unit.
+     *
+     * @return the set of providers; if the unit doesn't have any inheritance chains, then the set will contain only one {@link
+     * TypeProvider}
+     */
+    @NotNull Set<TypeProvider> getTypeProviders();
+
+    /**
+     * Retrieves an OSGi runtime dependency of the wrapped script identified by the passed {@code className} parameter.
+     *
+     * @param className the fully qualified class name
+     * @param <T>       the expected service type
+     * @return an instance of the {@link T} or {@code null}
+     */
+    @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 <T>       the expected service type
+     * @return an instance of the {@link T} or {@code null}
+     */
+    @Nullable <T> T[] getServices(@NotNull String className, @Nullable String filter);
+
+    /**
+     * 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();
+
+    /**
+     * This method will execute / evaluate the wrapped script or precompiled script with the given request.
+     *
+     * @throws Exception if the execution leads to an error
+     */
+    void eval(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws Exception;
+}
diff --git a/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/BundledScriptFinder.java b/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/BundledScriptFinder.java
index 17f11fa..1715481 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/BundledScriptFinder.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/BundledScriptFinder.java
@@ -25,8 +25,8 @@
 
 public interface BundledScriptFinder {
 
-    Executable getScript(Set<TypeProvider> providers, Set<TypeProvider> allProviders);
+    BundledRenderUnit getScript(Set<TypeProvider> providers, Set<TypeProvider> allProviders);
 
-    Executable getScript(@NotNull Bundle bundle, @NotNull String path, @NotNull String scriptEngineName,
+    BundledRenderUnit getScript(@NotNull Bundle bundle, @NotNull String path, @NotNull String scriptEngineName,
                          @NotNull Set<TypeProvider> providers);
 }
diff --git a/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/Executable.java b/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/Executable.java
deleted file mode 100644
index 207bff4..0000000
--- a/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/Executable.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ~ Licensed to the Apache Software Foundation (ASF) under one
- ~ or more contributor license agreements.  See the NOTICE file
- ~ distributed with this work for additional information
- ~ regarding copyright ownership.  The ASF licenses this file
- ~ to you under the Apache License, Version 2.0 (the
- ~ "License"); you may not use this file except in compliance
- ~ with the License.  You may obtain a copy of the License at
- ~
- ~   http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing,
- ~ software distributed under the License is distributed on an
- ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- ~ KIND, either express or implied.  See the License for the
- ~ specific language governing permissions and limitations
- ~ under the License.
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-package org.apache.sling.servlets.resolver.bundle.tracker;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.jetbrains.annotations.NotNull;
-
-public interface Executable {
-
-    /**
-     * 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();
-
-    /**
-     * This method will execute / evaluate the wrapped script or precompiled script with the given request.
-     *
-     * @throws Exception if the execution leads to an error
-     */
-    void eval(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws Exception;
-}
diff --git a/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/internal/BundledScriptServlet.java b/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/internal/BundledScriptServlet.java
index f012634..026184b 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/internal/BundledScriptServlet.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/internal/BundledScriptServlet.java
@@ -31,7 +31,7 @@
 import org.apache.sling.api.SlingConstants;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.SlingHttpServletResponse;
-import org.apache.sling.servlets.resolver.bundle.tracker.Executable;
+import org.apache.sling.servlets.resolver.bundle.tracker.BundledRenderUnit;
 import org.apache.sling.servlets.resolver.bundle.tracker.TypeProvider;
 import org.apache.sling.servlets.resolver.bundle.tracker.internal.request.RequestWrapper;
 import org.jetbrains.annotations.NotNull;
@@ -39,11 +39,11 @@
 class BundledScriptServlet extends GenericServlet {
 
     private final LinkedHashSet<TypeProvider> wiredTypeProviders;
-    private final Executable executable;
+    private final BundledRenderUnit executable;
 
 
     BundledScriptServlet(@NotNull LinkedHashSet<TypeProvider> wiredTypeProviders,
-                         @NotNull Executable executable) {
+                         @NotNull BundledRenderUnit executable) {
         this.wiredTypeProviders = wiredTypeProviders;
         this.executable = executable;
     }
diff --git a/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/internal/BundledScriptTracker.java b/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/internal/BundledScriptTracker.java
index 318262d..58bd6ac 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/internal/BundledScriptTracker.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/bundle/tracker/internal/BundledScriptTracker.java
@@ -48,8 +48,8 @@
 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.servlets.resolver.bundle.tracker.BundledRenderUnit;
 import org.apache.sling.servlets.resolver.bundle.tracker.BundledScriptFinder;
-import org.apache.sling.servlets.resolver.bundle.tracker.Executable;
 import org.apache.sling.servlets.resolver.bundle.tracker.ResourceType;
 import org.apache.sling.servlets.resolver.bundle.tracker.BundledRenderUnitCapability;
 import org.apache.sling.servlets.resolver.bundle.tracker.TypeProvider;
@@ -131,7 +131,7 @@
                     properties.put(ServletResolverConstants.SLING_SERVLET_NAME, BundledScriptServlet.class.getName());
                     properties.put(Constants.SERVICE_DESCRIPTION, BundledScriptServlet.class.getName() + cap.getAttributes());
                     BundledRenderUnitCapability bundledRenderUnitCapability = BundledRenderUnitCapabilityImpl.fromBundleCapability(cap);
-                    Executable executable = null;
+                    BundledRenderUnit executable = null;
                     TypeProvider baseTypeProvider = new TypeProviderImpl(bundledRenderUnitCapability, bundle);
                     LinkedHashSet<TypeProvider> inheritanceChain = new LinkedHashSet<>();
                     inheritanceChain.add(baseTypeProvider);
@@ -183,7 +183,7 @@
                     List<ServiceRegistration<Servlet>> regs = new ArrayList<>();
 
                     if (executable != null) {
-                        Executable finalExecutable = executable;
+                        BundledRenderUnit finalExecutable = executable;
                         bundledRenderUnitCapability.getResourceTypes().forEach(resourceType -> {
                             if (finalExecutable.getPath().startsWith(resourceType.toString() + "/")) {
                                 properties.put(ServletResolverConstants.SLING_SERVLET_PATHS, finalExecutable.getPath());
diff --git a/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletMounter.java b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletMounter.java
index bc772ab..6a112d7 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletMounter.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletMounter.java
@@ -143,16 +143,20 @@
             cardinality = ReferenceCardinality.MULTIPLE,
             policy = ReferencePolicy.DYNAMIC,
             target="(|(" + ServletResolverConstants.SLING_SERVLET_PATHS + "=*)(" + ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES + "=*))")
-    protected void bindServlet(final Servlet servlet, final ServiceReference<Servlet> reference) {
+    public void bindServlet(final Servlet servlet, final ServiceReference<Servlet> reference) {
         if (this.active) {
             createServlet(servlet, reference);
         }
     }
 
-    protected void unbindServlet(final ServiceReference<Servlet> reference) {
+    public void unbindServlet(final ServiceReference<Servlet> reference) {
         destroyServlet(reference);
     }
 
+    public boolean mountProviders() {
+        return provider == null;
+    }
+
     @Reference(
         name = REF_CACHE,
         service = ResolutionCache.class,