SLING-9306 - Provide a path capability for scripts which are not part of a resource type

* switched to using the ServletResolverConstants for generating the capabilities'
attributes
* capabilities providing only a path instead of a resource type will generate
a path-registered servlet
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 42f15e7..a9adcd5 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
@@ -44,7 +44,7 @@
 
     Executable getScript(Set<TypeProvider> providers) {
         for (TypeProvider provider : providers) {
-            ResourceTypeCapability capability = provider.getResourceTypeCapability();
+            ServletCapability capability = provider.getServletCapability();
             for (String match : buildScriptMatches(capability.getResourceTypes(),
                     capability.getSelectors().toArray(new String[0]), capability.getMethod(), capability.getExtension())) {
                 String scriptExtension = capability.getScriptExtension();
@@ -61,10 +61,7 @@
         return null;
     }
 
-    @Nullable
-    private Executable getExecutable(@NotNull Bundle bundle, boolean precompiled, @NotNull String match,
-                                     @NotNull String scriptEngineName, @NotNull String scriptExtension) {
-        String path = match + DOT + scriptExtension;
+    Executable getScript(@NotNull Bundle bundle, boolean precompiled, @NotNull String path, @NotNull String scriptEngineName) {
         if (precompiled) {
             String className = JavaEscapeHelper.makeJavaPackage(path);
             try {
@@ -74,7 +71,7 @@
                 // do nothing here
             }
         } else {
-            URL bundledScriptURL = bundle.getEntry(NS_JAVAX_SCRIPT_CAPABILITY + (match.startsWith("/") ? "" : SLASH) + path);
+            URL bundledScriptURL = bundle.getEntry(NS_JAVAX_SCRIPT_CAPABILITY + (path.startsWith("/") ? "" : SLASH) + path);
             if (bundledScriptURL != null) {
                 return new Script(bundle, path, bundledScriptURL, scriptEngineName);
             }
@@ -82,6 +79,13 @@
         return null;
     }
 
+    @Nullable
+    private Executable getExecutable(@NotNull Bundle bundle, boolean precompiled, @NotNull String match,
+                                     @NotNull String scriptEngineName, @NotNull String scriptExtension) {
+        String path = match + DOT + scriptExtension;
+        return getScript(bundle, precompiled, path, scriptEngineName);
+    }
+
     private List<String> buildScriptMatches(Set<ResourceType> resourceTypes, String[] selectors, String method, String extension) {
         List<String> matches = new ArrayList<>();
         for (ResourceType resourceType : resourceTypes) {
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 e053a58..e9e7b8e 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
@@ -67,7 +67,7 @@
             }
 
             RequestWrapper requestWrapper = new RequestWrapper(request,
-                    wiredTypeProviders.stream().map(typeProvider -> typeProvider.getResourceTypeCapability().getResourceTypes()
+                    wiredTypeProviders.stream().map(typeProvider -> typeProvider.getServletCapability().getResourceTypes()
             ).flatMap(Collection::stream).collect(Collectors.toSet()));
             ScriptContextProvider.ExecutableContext executableContext = scriptContextProvider
                     .prepareScriptContext(requestWrapper, response, executable);
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 9006823..0cab4c6 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
@@ -30,7 +30,6 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -82,10 +81,8 @@
 public class BundledScriptTracker implements BundleTrackerCustomizer<List<ServiceRegistration<Servlet>>> {
     static final String NS_SLING_SCRIPTING_EXTENDER = "sling.scripting";
 
-    static final String NS_SLING_RESOURCE_TYPE = "sling.resourceType";
+    static final String NS_SLING_SERVLET = "sling.servlet";
     private static final Logger LOGGER = LoggerFactory.getLogger(BundledScriptTracker.class);
-    static final String AT_SLING_SELECTORS = "sling.resourceType.selectors";
-    static final String AT_SLING_EXTENSIONS = "sling.resourceType.extensions";
     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";
@@ -120,51 +117,61 @@
         BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
         if (bundleWiring.getRequiredWires("osgi.extender").stream().map(BundleWire::getProvider).map(BundleRevision::getBundle)
                 .anyMatch(m_context.getBundle()::equals)) {
-            LOGGER.debug("Inspecting bundle {} for {} capability.", bundle.getSymbolicName(), NS_SLING_RESOURCE_TYPE);
-            List<BundleCapability> capabilities = bundleWiring.getCapabilities(NS_SLING_RESOURCE_TYPE);
+            LOGGER.debug("Inspecting bundle {} for {} capability.", bundle.getSymbolicName(), NS_SLING_SERVLET);
+            List<BundleCapability> capabilities = bundleWiring.getCapabilities(NS_SLING_SERVLET);
             if (!capabilities.isEmpty()) {
                 List<ServiceRegistration<Servlet>> serviceRegistrations = capabilities.stream().flatMap(cap ->
                 {
                     Hashtable<String, Object> properties = new Hashtable<>();
                     properties.put(ServletResolverConstants.SLING_SERVLET_NAME, BundledScriptServlet.class.getName());
                     properties.put(Constants.SERVICE_DESCRIPTION, BundledScriptServlet.class.getName() + cap.getAttributes());
-                    ResourceTypeCapability resourceTypeCapability = ResourceTypeCapability.fromBundleCapability(cap);
-                    String[] resourceTypesRegistrationValue = new String[resourceTypeCapability.getResourceTypes().size()];
-                    int rtIndex = 0;
-                    for (ResourceType resourceType : resourceTypeCapability.getResourceTypes()) {
-                        resourceTypesRegistrationValue[rtIndex++] = resourceType.toString();
-                    }
-                    properties.put(ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES, resourceTypesRegistrationValue);
-
-                    String extension = resourceTypeCapability.getExtension();
-                    if (StringUtils.isEmpty(extension)) {
-                        extension = "html";
-                    }
-                    properties.put(ServletResolverConstants.SLING_SERVLET_EXTENSIONS, extension);
-
-                    if (!resourceTypeCapability.getSelectors().isEmpty()) {
-                        properties.put(ServletResolverConstants.SLING_SERVLET_SELECTORS, resourceTypeCapability.getSelectors().toArray());
-                    }
-
-                    if (StringUtils.isNotEmpty(resourceTypeCapability.getMethod())) {
-                        properties.put(ServletResolverConstants.SLING_SERVLET_METHODS, resourceTypeCapability.getMethod());
-                    }
-
-                    List<ServiceRegistration<Servlet>> regs = new ArrayList<>();
+                    ServletCapability servletCapability = ServletCapability.fromBundleCapability(cap);
+                    Executable executable = null;
+                    TypeProvider baseTypeProvider = new TypeProvider(servletCapability, bundle);
                     LinkedHashSet<TypeProvider> wiredProviders = new LinkedHashSet<>();
-                    wiredProviders.add(new TypeProvider(resourceTypeCapability, bundle));
-                    String extendedResourceTypeString = resourceTypeCapability.getExtendedResourceType();
-                    if (StringUtils.isNotEmpty(extendedResourceTypeString)) {
-                        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());
-                                }
-                            }
-                        });
+                    wiredProviders.add(baseTypeProvider);
+                    if (!servletCapability.getResourceTypes().isEmpty()) {
+                        String[] resourceTypesRegistrationValue = new String[servletCapability.getResourceTypes().size()];
+                        int rtIndex = 0;
+                        for (ResourceType resourceType : servletCapability.getResourceTypes()) {
+                            resourceTypesRegistrationValue[rtIndex++] = resourceType.toString();
+                        }
+                        properties.put(ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES, resourceTypesRegistrationValue);
+
+                        String extension = servletCapability.getExtension();
+                        if (StringUtils.isEmpty(extension)) {
+                            extension = "html";
+                        }
+                        properties.put(ServletResolverConstants.SLING_SERVLET_EXTENSIONS, extension);
+
+                        if (!servletCapability.getSelectors().isEmpty()) {
+                            properties.put(ServletResolverConstants.SLING_SERVLET_SELECTORS, servletCapability.getSelectors().toArray());
+                        }
+
+                        if (StringUtils.isNotEmpty(servletCapability.getMethod())) {
+                            properties.put(ServletResolverConstants.SLING_SERVLET_METHODS, servletCapability.getMethod());
+                        }
+
+                        String extendedResourceTypeString = servletCapability.getExtendedResourceType();
+                        if (StringUtils.isNotEmpty(extendedResourceTypeString)) {
+                            collectProvidersChain(wiredProviders, bundleWiring, extendedResourceTypeString);
+                            wiredProviders.stream().filter(typeProvider -> typeProvider.getServletCapability().getResourceTypes().stream()
+                                    .anyMatch(resourceType -> resourceType.getType().equals(extendedResourceTypeString))).findFirst()
+                                    .ifPresent(typeProvider -> {
+                                        for (ResourceType type : typeProvider.getServletCapability().getResourceTypes()) {
+                                            if (type.getType().equals(extendedResourceTypeString)) {
+                                                properties.put(ServletResolverConstants.SLING_SERVLET_RESOURCE_SUPER_TYPE, type.toString());
+                                            }
+                                        }
+                                    });
+                        }
+                        executable = bundledScriptFinder.getScript(wiredProviders);
+                    } else if (StringUtils.isNotEmpty(servletCapability.getPath()) && StringUtils.isNotEmpty(servletCapability.getScriptEngineName())) {
+                        executable = bundledScriptFinder.getScript(baseTypeProvider.getBundle(), baseTypeProvider.isPrecompiled(),
+                                servletCapability.getPath(), servletCapability.getScriptEngineName());
                     }
-                    Executable executable = bundledScriptFinder.getScript(wiredProviders);
+                    List<ServiceRegistration<Servlet>> regs = new ArrayList<>();
+
                     if (executable != null) {
                         properties.put(ServletResolverConstants.SLING_SERVLET_PATHS, executable.getPath());
                         regs.add(
@@ -351,9 +358,12 @@
 
     private static String getResourceTypeVersion(ServiceReference<?> ref) {
         String[] values = PropertiesUtil.toStringArray(ref.getProperty(ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES));
-        String resourceTypeValue = values[0];
-        ResourceType resourceType = ResourceType.parseResourceType(resourceTypeValue);
-        return resourceType.getVersion();
+        if (values != null) {
+            String resourceTypeValue = values[0];
+            ResourceType resourceType = ResourceType.parseResourceType(resourceTypeValue);
+            return resourceType.getVersion();
+        }
+        return null;
     }
 
     private static Set<String> getResourceTypes(Hashtable<String, Object> props) {
@@ -367,26 +377,26 @@
 
     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());
+        for (BundleWire wire : wiring.getRequiredWires(NS_SLING_SERVLET)) {
+            ServletCapability wiredCapability = ServletCapability.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()) {
+                        for (BundleWire providedWire : wire.getProvider().getWiring().getRequiredWires(NS_SLING_SERVLET)) {
+                            ServletCapability servletCapability =
+                                    ServletCapability.fromBundleCapability(providedWire.getCapability());
+                            String capabilityExtends = servletCapability.getExtendedResourceType();
+                            if (servletCapability.getSelectors().isEmpty() && StringUtils.isNotEmpty(capabilityExtends)) {
+                                for (ResourceType providedResourceType : servletCapability.getResourceTypes()) {
                                     if (providedResourceType.getType().equals(extendedResourceType)) {
                                         collectProvidersChain(providers, providedWire.getProvider()
                                                 .getBundle().adapt(BundleWiring.class), capabilityExtends);
                                     }
                                 }
                             } else {
-                                providers.add(new TypeProvider(resourceTypeCapability, providedWire.getProvider().getBundle()));
+                                providers.add(new TypeProvider(servletCapability, providedWire.getProvider().getBundle()));
                             }
                         }
                     }
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/ServletCapability.java
similarity index 76%
rename from src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceTypeCapability.java
rename to src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ServletCapability.java
index 756750c..f782fe4 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/ServletCapability.java
@@ -34,9 +34,10 @@
 import org.osgi.framework.Version;
 import org.osgi.framework.wiring.BundleCapability;
 
-class ResourceTypeCapability {
+class ServletCapability {
 
     private final Set<ResourceType> resourceTypes;
+    private final String path;
     private final List<String> selectors;
     private final String extension;
     private final String method;
@@ -44,11 +45,12 @@
     private final String scriptEngineName;
     private final String scriptExtension;
 
-    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) {
+    private ServletCapability(@NotNull Set<ResourceType> resourceTypes, @Nullable String path, @NotNull List<String> selectors,
+                              @Nullable String extension, @Nullable String method,
+                              @Nullable String extendedResourceType, @Nullable String scriptEngineName,
+                              @Nullable String scriptExtension) {
         this.resourceTypes = resourceTypes;
+        this.path = path;
         this.selectors = selectors;
         this.extension = extension;
         this.method = method;
@@ -62,6 +64,11 @@
         return Collections.unmodifiableSet(resourceTypes);
     }
 
+    @Nullable
+    public String getPath() {
+        return path;
+    }
+
     @NotNull
     List<String> getSelectors() {
         return Collections.unmodifiableList(selectors);
@@ -94,7 +101,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(resourceTypes, selectors, extension, method, extendedResourceType, scriptEngineName, scriptExtension);
+        return Objects.hash(resourceTypes, path, selectors, extension, method, extendedResourceType, scriptEngineName, scriptExtension);
     }
 
     @Override
@@ -102,9 +109,10 @@
         if (this == obj) {
             return true;
         }
-        if (obj instanceof ResourceTypeCapability) {
-            ResourceTypeCapability other = (ResourceTypeCapability) obj;
-            return Objects.equals(resourceTypes, other.resourceTypes) && Objects.equals(selectors, other.selectors) &&
+        if (obj instanceof ServletCapability) {
+            ServletCapability other = (ServletCapability) obj;
+            return Objects.equals(resourceTypes, other.resourceTypes) && Objects.equals(path, other.path) &&
+                    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);
@@ -112,10 +120,10 @@
         return false;
     }
 
-    static ResourceTypeCapability fromBundleCapability(@NotNull BundleCapability capability) {
+    static ServletCapability fromBundleCapability(@NotNull BundleCapability capability) {
         Map<String, Object> attributes = capability.getAttributes();
         Set<ResourceType> resourceTypes = new HashSet<>();
-        String[] capabilityResourceTypes = PropertiesUtil.toStringArray(attributes.get(BundledScriptTracker.NS_SLING_RESOURCE_TYPE),
+        String[] capabilityResourceTypes = PropertiesUtil.toStringArray(attributes.get(ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES),
                 new String[0]);
         Version version = (Version) attributes.get(BundledScriptTracker.AT_VERSION);
         for (String rt : capabilityResourceTypes) {
@@ -125,10 +133,11 @@
                 resourceTypes.add(ResourceType.parseResourceType(rt + "/" + version.toString()));
             }
         }
-        return new ResourceTypeCapability(
+        return new ServletCapability(
                 resourceTypes,
-                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_PATHS),
+                Arrays.asList(PropertiesUtil.toStringArray(attributes.get(ServletResolverConstants.SLING_SERVLET_SELECTORS), new String[0])),
+                (String) attributes.get(ServletResolverConstants.SLING_SERVLET_EXTENSIONS),
                 (String) attributes.get(ServletResolverConstants.SLING_SERVLET_METHODS),
                 (String) attributes.get(BundledScriptTracker.AT_EXTENDS),
                 (String) attributes.get(BundledScriptTracker.AT_SCRIPT_ENGINE),
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 daa4bdf..ee6c643 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
@@ -27,18 +27,18 @@
  */
 public class TypeProvider {
 
-    private final ResourceTypeCapability resourceTypeCapability;
+    private final ServletCapability servletCapability;
     private final Bundle bundle;
     private final boolean precompiled;
 
     /**
      * Builds a {@code TypeProvider}.
      *
-     * @param resourceTypeCapability  the resource type capability
+     * @param servletCapability  the resource type capability
      * @param bundle the bundle that provides the resource type
      */
-    TypeProvider(ResourceTypeCapability resourceTypeCapability, Bundle bundle) {
-        this.resourceTypeCapability = resourceTypeCapability;
+    TypeProvider(ServletCapability servletCapability, Bundle bundle) {
+        this.servletCapability = servletCapability;
         this.bundle = bundle;
         precompiled = Boolean.parseBoolean(bundle.getHeaders().get("Sling-ResourceType-Precompiled"));
     }
@@ -48,8 +48,8 @@
      *
      * @return the resource type capabilities
      */
-    ResourceTypeCapability getResourceTypeCapability() {
-        return resourceTypeCapability;
+    ServletCapability getServletCapability() {
+        return servletCapability;
     }
 
     /**
@@ -72,7 +72,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(bundle, resourceTypeCapability, precompiled);
+        return Objects.hash(bundle, servletCapability, precompiled);
     }
 
     @Override
@@ -82,7 +82,7 @@
         }
         if (obj instanceof TypeProvider) {
             TypeProvider other = (TypeProvider) obj;
-            return Objects.equals(bundle, other.bundle) && Objects.equals(resourceTypeCapability, other.resourceTypeCapability) &&
+            return Objects.equals(bundle, other.bundle) && Objects.equals(servletCapability, other.servletCapability) &&
                     Objects.equals(precompiled, other.precompiled);
         }
         return false;
@@ -90,7 +90,7 @@
 
     @Override
     public String toString() {
-        return String.format("TypeProvider{ resourceTypeCapability=%s; bundle=%s; precompiled=%s }", resourceTypeCapability,
+        return String.format("TypeProvider{ resourceTypeCapability=%s; bundle=%s; precompiled=%s }", servletCapability,
                 bundle.getSymbolicName(), precompiled);
     }
 }