SLING-9216 - Allow capabilities to provide lists of resource types
diff --git a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceType.java b/src/main/java/org/apache/sling/scripting/bundle/tracker/ResourceType.java
similarity index 95%
rename from src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceType.java
rename to src/main/java/org/apache/sling/scripting/bundle/tracker/ResourceType.java
index a8b6ff5..263879b 100644
--- a/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceType.java
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/ResourceType.java
@@ -16,7 +16,7 @@
  ~ specific language governing permissions and limitations
  ~ under the License.
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-package org.apache.sling.scripting.bundle.tracker.internal;
+package org.apache.sling.scripting.bundle.tracker;
 
 import java.util.Objects;
 
@@ -37,7 +37,7 @@
  * <li><tt>a</tt> - flat (sub-set of path-based)</li>
  * </ol>
  */
-final class ResourceType {
+public final class ResourceType {
 
     private final String type;
     private final String version;
@@ -74,7 +74,7 @@
      * @return the resource type string
      */
     @NotNull
-    String getType() {
+    public String getType() {
         return type;
     }
 
@@ -84,7 +84,7 @@
      * @return the version, if available; {@code null} otherwise
      */
     @Nullable
-    String getVersion() {
+    public String getVersion() {
         return version;
     }
 
@@ -109,7 +109,7 @@
      * @throws IllegalArgumentException if the {@code resourceTypeString} cannot be parsed
      */
     @NotNull
-    static ResourceType parseResourceType(@NotNull String resourceTypeString) {
+    public static ResourceType parseResourceType(@NotNull String resourceTypeString) {
         String type = StringUtils.EMPTY;
         String version = null;
         if (StringUtils.isNotEmpty(resourceTypeString)) {
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 0c6b926..162c89d 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
@@ -25,7 +25,6 @@
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 import javax.script.ScriptEngine;
@@ -34,11 +33,9 @@
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.api.SlingHttpServletRequest;
-import org.apache.sling.api.servlets.ServletResolverConstants;
 import org.apache.sling.commons.compiler.source.JavaEscapeHelper;
-import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.scripting.bundle.tracker.ResourceType;
 import org.osgi.framework.Bundle;
-import org.osgi.framework.Version;
 import org.osgi.framework.wiring.BundleCapability;
 import org.osgi.framework.wiring.BundleWiring;
 import org.osgi.service.component.annotations.Component;
@@ -59,7 +56,7 @@
     Executable getScript(SlingHttpServletRequest request, LinkedHashSet<TypeProvider> typeProviders, boolean precompiledScripts) {
         List<String> scriptMatches;
         for (TypeProvider provider : typeProviders) {
-            scriptMatches = buildScriptMatches(request, provider.getResourceType());
+            scriptMatches = buildScriptMatches(request, provider.getResourceTypes());
             String scriptEngineName = getScriptEngineName(request, provider);
             if (StringUtils.isNotEmpty(scriptEngineName)) {
                 ScriptEngine scriptEngine = scriptEngineManager.getEngineByName(scriptEngineName);
@@ -77,7 +74,7 @@
                                 } catch (ClassNotFoundException e) {
                                     // do nothing here
                                 } catch (Exception e) {
-                                    throw new RuntimeException("Cannot correctly instantiate class " + className + ".");
+                                    throw new IllegalStateException("Cannot correctly instantiate class " + className + ".");
                                 }
                             } else {
                                 bundledScriptURL =
@@ -95,37 +92,40 @@
         return null;
     }
 
-    private List<String> buildScriptMatches(SlingHttpServletRequest request, ResourceType resourceType) {
+    private List<String> buildScriptMatches(SlingHttpServletRequest request, Set<ResourceType> resourceTypes) {
         List<String> matches = new ArrayList<>();
         String method = request.getMethod();
         String extension = request.getRequestPathInfo().getExtension();
         String[] selectors = request.getRequestPathInfo().getSelectors();
-        if (selectors.length > 0) {
-            for (int i = selectors.length - 1; i >= 0; i--) {
-                String base =
-                resourceType.getType() +
-                        (StringUtils.isNotEmpty(resourceType.getVersion()) ? SLASH + resourceType.getVersion() + SLASH : SLASH) +
-                        String.join(SLASH, Arrays.copyOf(selectors, i + 1));
-                if (StringUtils.isNotEmpty(extension)){
-                    matches.add(base + DOT + extension + DOT + method);
-                    matches.add(base + DOT + extension);
+        for (ResourceType resourceType : resourceTypes) {
+            if (selectors.length > 0) {
+                for (int i = selectors.length - 1; i >= 0; i--) {
+                    String base =
+                            resourceType.getType() +
+                                    (StringUtils.isNotEmpty(resourceType.getVersion()) ? SLASH + resourceType.getVersion() + SLASH :
+                                            SLASH) +
+                                    String.join(SLASH, Arrays.copyOf(selectors, i + 1));
+                    if (StringUtils.isNotEmpty(extension)) {
+                        matches.add(base + DOT + extension + DOT + method);
+                        matches.add(base + DOT + extension);
+                    }
+                    matches.add(base + DOT + method);
+                    matches.add(base);
                 }
-                matches.add(base + DOT + method);
-                matches.add(base);
             }
-        }
-        String base = resourceType.getType() +
-                (StringUtils.isNotEmpty(resourceType.getVersion()) ? SLASH + resourceType.getVersion() : StringUtils.EMPTY);
+            String base = resourceType.getType() +
+                    (StringUtils.isNotEmpty(resourceType.getVersion()) ? SLASH + resourceType.getVersion() : StringUtils.EMPTY);
 
-        if (StringUtils.isNotEmpty(extension)) {
-            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);
-        matches.add(base + SLASH + resourceType.getResourceLabel());
-        matches.add(base + SLASH + method);
-        if (StringUtils.isNotEmpty(extension)) {
-            matches.add(base + SLASH + extension);
+            if (StringUtils.isNotEmpty(extension)) {
+                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);
+            matches.add(base + SLASH + resourceType.getResourceLabel());
+            matches.add(base + SLASH + method);
+            if (StringUtils.isNotEmpty(extension)) {
+                matches.add(base + SLASH + extension);
+            }
         }
         return Collections.unmodifiableList(matches);
     }
@@ -139,27 +139,21 @@
         String requestExtension = request.getRequestPathInfo().getExtension();
         String requestMethod = request.getMethod();
         for (BundleCapability capability : capabilities) {
-            Map<String, Object> attributes = capability.getAttributes();
-            if (typeProvider.getResourceType().getType().equals(attributes.get(BundledScriptTracker.NS_SLING_RESOURCE_TYPE)) && Arrays.equals(selectors,
-                    PropertiesUtil.toStringArray(attributes.get(BundledScriptTracker.AT_SLING_SELECTORS), new String[]{}))) {
-                String version = typeProvider.getResourceType().getVersion();
-                Version capabilityVersion = (Version) attributes.get(BundledScriptTracker.AT_VERSION);
-                if (version != null && capabilityVersion!= null && !version.equals(capabilityVersion.toString())) {
-                    continue;
-                }
-                Set<String> capabilityRequestExtensions = new HashSet<>(
-                        Arrays.asList(PropertiesUtil.toStringArray(attributes.get(BundledScriptTracker.AT_SLING_EXTENSIONS), new String[0]))
-                );
-                Set<String> capabilityRequestMethods = new HashSet<>(
-                        Arrays.asList(
-                                PropertiesUtil.toStringArray(attributes.get(ServletResolverConstants.SLING_SERVLET_METHODS), new String[0]))
-                );
+            ResourceTypeCapability resourceTypeCapability = ResourceTypeCapability.fromBundleCapability(capability);
+            for (ResourceType resourceType : typeProvider.getResourceTypes()) {
                 if (
-                    ((capabilityRequestExtensions.isEmpty() && "html".equals(requestExtension)) || capabilityRequestExtensions.contains(requestExtension)) &&
-                    ((capabilityRequestMethods.isEmpty() && ("GET".equals(requestMethod) || "HEAD".equals(requestMethod))) || capabilityRequestMethods.contains(requestMethod)) &&
-                    StringUtils.isEmpty(scriptEngineName)
+                        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 = (String) attributes.get(BundledScriptTracker.AT_SCRIPT_ENGINE);
+                    scriptEngineName = resourceTypeCapability.getScriptEngineName();
+                    if (scriptEngineName != null) {
+                        break;
+                    }
                 }
             }
         }
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 8d8a0e1..2cf4be3 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
@@ -42,6 +42,7 @@
 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.bundle.tracker.ResourceType;
 import org.apache.sling.scripting.core.ScriptHelper;
 
 class BundledScriptServlet extends GenericServlet {
@@ -108,7 +109,9 @@
             if (executable != null) {
                 Set<String> wiredResourceTypes = new HashSet<>();
                 for (TypeProvider typeProvider : m_wiredTypeProviders) {
-                    wiredResourceTypes.add(typeProvider.getResourceType().toString());
+                    for (ResourceType resourceType : typeProvider.getResourceTypes()) {
+                        wiredResourceTypes.add(resourceType.toString());
+                    }
                 }
                 RequestWrapper requestWrapper = new RequestWrapper(request, wiredResourceTypes);
                 ScriptContext scriptContext = m_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 f9c1661..bbe4d15 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
@@ -49,6 +49,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.osgi.annotation.bundle.Capability;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
@@ -87,7 +88,7 @@
     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";
-    private static final String AT_EXTENDS = "extends";
+    static final String AT_EXTENDS = "extends";
 
     @Reference
     private BundledScriptFinder bundledScriptFinder;
@@ -126,64 +127,49 @@
                     Hashtable<String, Object> properties = new Hashtable<>();
                     properties.put(ServletResolverConstants.SLING_SERVLET_NAME, BundledScriptServlet.class.getName());
                     properties.put(Constants.SERVICE_DESCRIPTION, cap.toString());
-
-                    Map<String, Object> attributes = cap.getAttributes();
-                    String resourceType = (String) attributes.get(NS_SLING_RESOURCE_TYPE);
-                    String resourceTypeString = resourceType;
-
-                    Version version = (Version) attributes.get(AT_VERSION);
-
-                    if (version != null) {
-                        resourceTypeString += "/" + version;
+                    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);
 
-                    properties.put(ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES, resourceTypeString);
-
-                    Object selectors = attributes.get(AT_SLING_SELECTORS);
-                    Set<String> extensions = new HashSet<>(
-                            Arrays.asList(PropertiesUtil.toStringArray(attributes.get(AT_SLING_EXTENSIONS), new String[0]))
-                    );
+                    Set<String> extensions = new HashSet<>(resourceTypeCapability.getSelectors());
                     extensions.add("html");
-                    properties.put(ServletResolverConstants.SLING_SERVLET_EXTENSIONS, extensions);
+                    properties.put(ServletResolverConstants.SLING_SERVLET_EXTENSIONS, extensions.toArray());
 
-                    if (selectors != null) {
-                        properties.put(ServletResolverConstants.SLING_SERVLET_SELECTORS, selectors);
+                    if (!resourceTypeCapability.getSelectors().isEmpty()) {
+                        properties.put(ServletResolverConstants.SLING_SERVLET_SELECTORS, resourceTypeCapability.getSelectors().toArray());
                     }
 
-                    Set<String> methods = new HashSet<>(Arrays.asList(
-                            PropertiesUtil.toStringArray(attributes.get(ServletResolverConstants.SLING_SERVLET_METHODS), new String[0])));
-                    if (!methods.isEmpty()) {
-                        properties.put(ServletResolverConstants.SLING_SERVLET_METHODS, String.join(",", methods));
-                    }
-
-                    String extendsRT = (String) attributes.get(AT_EXTENDS);
-                    Optional<BundleWire> optionalWire = Optional.empty();
-
-                    if (StringUtils.isNotEmpty(extendsRT)) {
-
-                        LOGGER.debug("Bundle {} extends resource type {} through {}.", bundle.getSymbolicName(), extendsRT,
-                                resourceTypeString);
-                        optionalWire = bundleWiring.getRequiredWires(NS_SLING_RESOURCE_TYPE).stream().filter(
-                                bundleWire -> extendsRT.equals(bundleWire.getCapability().getAttributes().get(NS_SLING_RESOURCE_TYPE)) &&
-                                        !bundleWire.getCapability().getAttributes().containsKey(AT_SLING_SELECTORS)
-                        ).findFirst();
+                    if (!resourceTypeCapability.getMethods().isEmpty()) {
+                        properties.put(ServletResolverConstants.SLING_SERVLET_METHODS, resourceTypeCapability.getMethods().toArray());
                     }
 
                     List<ServiceRegistration<Servlet>> regs = new ArrayList<>();
                     LinkedHashSet<TypeProvider> wiredProviders = new LinkedHashSet<>();
-                    wiredProviders.add(new TypeProvider(ResourceType.parseResourceType(resourceType), bundle));
-                    wiredProviders.add(new TypeProvider(ResourceType.parseResourceType(resourceTypeString), bundle));
-                    if (optionalWire.isPresent()) {
-                        BundleWire extendsWire = optionalWire.get();
-                        Bundle providerBundle = extendsWire.getProvider().getBundle();
-                        Map<String, Object> wireCapabilityAttributes = extendsWire.getCapability().getAttributes();
-                        String wireResourceType = (String) wireCapabilityAttributes.get(NS_SLING_RESOURCE_TYPE);
-                        Version wireResourceTypeVersion = (Version) wireCapabilityAttributes.get(AT_VERSION);
-                        String wireResourceTypeString = wireResourceType + (wireResourceTypeVersion != null ? "/" +
-                                wireResourceTypeVersion.toString() : "");
-                        wiredProviders.add(new TypeProvider(ResourceType.parseResourceType(wireResourceType), providerBundle));
-                        wiredProviders.add(new TypeProvider(ResourceType.parseResourceType(wireResourceTypeString), providerBundle));
-                        properties.put(ServletResolverConstants.SLING_SERVLET_RESOURCE_SUPER_TYPE, wireResourceTypeString);
+                    wiredProviders.add(new TypeProvider(resourceTypeCapability.getResourceTypes(), bundle));
+                    String extendedResourceType = resourceTypeCapability.getExtendedResourceType();
+                    if (StringUtils.isNotEmpty(extendedResourceType)) {
+                        BundleWire extendedWire = null;
+                        ResourceTypeCapability extendedCapability = null;
+                        LOGGER.debug("Bundle {} extends resource type {} through {}.", bundle.getSymbolicName(), extendedResourceType,
+                                resourceTypesRegistrationValue);
+                        for (BundleWire wire : bundleWiring.getRequiredWires(NS_SLING_RESOURCE_TYPE)) {
+                            ResourceTypeCapability wiredCapability =
+                                    ResourceTypeCapability.fromBundleCapability(wire.getCapability());
+                            for (ResourceType resourceType : wiredCapability.getResourceTypes()) {
+                                if (extendedResourceType.equals(resourceType.getType()) && wiredCapability.getSelectors().isEmpty()) {
+                                    extendedWire = wire;
+                                    extendedCapability = wiredCapability;
+                                }
+                            }
+                        }
+                        if (extendedWire != null) {
+                            wiredProviders.add(new TypeProvider(extendedCapability.getResourceTypes(), extendedWire.getProvider().getBundle()));
+                            properties.put(ServletResolverConstants.SLING_SERVLET_RESOURCE_SUPER_TYPE, extendedResourceType);
+                        }
                     }
                     populateWiredProviders(wiredProviders);
                     regs.add(
@@ -198,34 +184,35 @@
                 refreshDispatcher(serviceRegistrations);
                 return serviceRegistrations;
             } else {
-                return null;
+                return Collections.emptyList();
             }
         } else {
-            return null;
+            return Collections.emptyList();
         }
     }
 
 
     private void populateWiredProviders(LinkedHashSet<TypeProvider> initialProviders) {
-        Set<String> initialResourceTypes = new HashSet<>(initialProviders.size());
+        Set<ResourceType> initialResourceTypes = new HashSet<>();
         Set<Bundle> bundles = new HashSet<>(initialProviders.size());
-        for (TypeProvider provider : initialProviders) {
-            initialResourceTypes.add(provider.getResourceType().toString());
-            bundles.add(provider.getBundle());
-        }
+        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 ->
                     {
-                        String resourceType = (String) bundleWire.getCapability().getAttributes().get(BundledScriptTracker
-                                .NS_SLING_RESOURCE_TYPE);
-                        Version version = (Version) bundleWire.getCapability().getAttributes().get(BundledScriptTracker
-                                .AT_VERSION);
-                        if (!initialResourceTypes.contains(resourceType)) {
-                            initialProviders.add(new TypeProvider(
-                                    ResourceType.parseResourceType(resourceType + (version == null ? "" : "/" + version.toString())),
-                                    bundleWire.getProvider().getBundle()));
+                        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()));
                         }
                     }
             );
@@ -295,13 +282,6 @@
         return resourceType.getType();
     }
 
-    private 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();
-    }
-
     private void set(String key, ServiceReference<?> ref, Hashtable<String, Object> props) {
         Object value = ref.getProperty(key);
         if (value != null) {
@@ -311,14 +291,14 @@
 
     @Override
     public void modifiedBundle(Bundle bundle, BundleEvent event, List<ServiceRegistration<Servlet>> regs) {
-        LOGGER.warn(String.format("Unexpected modified event: %s for bundle %s", event.toString(), bundle.toString()));
+        LOGGER.warn("Unexpected modified event {} for bundle {}.", event.toString(), bundle.toString());
     }
 
     @Override
     public void removedBundle(Bundle bundle, BundleEvent event, List<ServiceRegistration<Servlet>> regs) {
         LOGGER.debug("Bundle {} removed", bundle.getSymbolicName());
         regs.forEach(ServiceRegistration::unregister);
-        refreshDispatcher(Collections.EMPTY_LIST);
+        refreshDispatcher(Collections.emptyList());
     }
 
     private class DispatcherServlet extends GenericServlet {
@@ -373,28 +353,41 @@
                     .findFirst();
 
             if (target.isPresent()) {
-                String rt = (String) target.get().getReference().getProperty(ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES);
-                RequestDispatcherOptions options = new RequestDispatcherOptions();
-                options.setForceResourceType(rt);
+                String[] targetRT =
+                        PropertiesUtil.toStringArray(target.get().getReference().getProperty(ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES));
+                if (targetRT == null || targetRT.length == 0) {
+                    ((SlingHttpServletResponse) res).sendError(HttpServletResponse.SC_NOT_FOUND);
+                } else {
+                    String rt = targetRT[0];
+                    RequestDispatcherOptions options = new RequestDispatcherOptions();
+                    options.setForceResourceType(rt);
 
-                RequestDispatcher dispatcher = slingRequest.getRequestDispatcher(slingRequest.getResource(), options);
-                if (dispatcher != null) {
-                    if (slingRequest.getAttribute(SlingConstants.ATTR_INCLUDE_SERVLET_PATH) == null) {
-                        final String contentType = slingRequest.getResponseContentType();
-                        if (contentType != null) {
-                            res.setContentType(contentType);
-                            if (contentType.startsWith("text/")) {
-                                res.setCharacterEncoding("UTF-8");
+                    RequestDispatcher dispatcher = slingRequest.getRequestDispatcher(slingRequest.getResource(), options);
+                    if (dispatcher != null) {
+                        if (slingRequest.getAttribute(SlingConstants.ATTR_INCLUDE_SERVLET_PATH) == null) {
+                            final String contentType = slingRequest.getResponseContentType();
+                            if (contentType != null) {
+                                res.setContentType(contentType);
+                                if (contentType.startsWith("text/")) {
+                                    res.setCharacterEncoding("UTF-8");
+                                }
                             }
                         }
+                        dispatcher.include(req, res);
+                    } else {
+                        ((SlingHttpServletResponse) res).sendError(HttpServletResponse.SC_NOT_FOUND);
                     }
-                    dispatcher.include(req, res);
-                } else {
-                    ((SlingHttpServletResponse) res).sendError(HttpServletResponse.SC_NOT_FOUND);
                 }
             } else {
                 ((SlingHttpServletResponse) res).sendError(HttpServletResponse.SC_NOT_FOUND);
             }
         }
+
+        private 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();
+        }
     }
 }
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
new file mode 100644
index 0000000..9648dc1
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceTypeCapability.java
@@ -0,0 +1,110 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.scripting.bundle.tracker.internal;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+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.jetbrains.annotations.Nullable;
+import org.osgi.framework.Version;
+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 String extendedResourceType;
+    private final String scriptEngineName;
+
+    private ResourceTypeCapability(@NotNull Set<ResourceType> resourceTypes, @NotNull Set<String> selectors,
+                                   @NotNull Set<String> extensions, @NotNull Set<String> methods,
+                                   @Nullable String extendedResourceType, @Nullable String scriptEngineName) {
+        this.resourceTypes = resourceTypes;
+        this.selectors = selectors;
+        this.extensions = extensions;
+        this.methods = methods;
+        this.extendedResourceType = extendedResourceType;
+        this.scriptEngineName = scriptEngineName;
+    }
+
+    @NotNull
+    Set<ResourceType> getResourceTypes() {
+        return Collections.unmodifiableSet(resourceTypes);
+    }
+
+    @NotNull
+    Set<String> getSelectors() {
+        return Collections.unmodifiableSet(selectors);
+    }
+
+    @NotNull
+    Set<String> getExtensions() {
+        return Collections.unmodifiableSet(extensions);
+    }
+
+    @Nullable
+    String getExtendedResourceType() {
+        return extendedResourceType;
+    }
+
+    @NotNull
+    Set<String> getMethods() {
+        return Collections.unmodifiableSet(methods);
+    }
+
+    @Nullable
+    String getScriptEngineName() {
+        return scriptEngineName;
+    }
+
+    static ResourceTypeCapability 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),
+                new String[0]);
+        Version version = (Version) attributes.get(BundledScriptTracker.AT_VERSION);
+        for (String rt : capabilityResourceTypes) {
+            if (version == null) {
+                resourceTypes.add(ResourceType.parseResourceType(rt));
+            } else {
+                resourceTypes.add(ResourceType.parseResourceType(rt + "/" + version.toString()));
+            }
+        }
+        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]))),
+                (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 befdc5f..7332102 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,7 +19,9 @@
 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;
 
 /**
@@ -27,17 +29,17 @@
  */
 public class TypeProvider {
 
-    private final ResourceType resourceType;
+    private final Set<ResourceType> resourceTypes;
     private final Bundle bundle;
 
     /**
      * Builds a {@code TypeProvider}.
      *
-     * @param resourceType   the resource type
+     * @param resourceTypes   the resource type
      * @param bundle the bundle that provides the resource type
      */
-    TypeProvider(ResourceType resourceType, Bundle bundle) {
-        this.resourceType = resourceType;
+    TypeProvider(Set<ResourceType> resourceTypes, Bundle bundle) {
+        this.resourceTypes = resourceTypes;
         this.bundle = bundle;
     }
 
@@ -46,8 +48,8 @@
      *
      * @return the resource type
      */
-    ResourceType getResourceType() {
-        return resourceType;
+    Set<ResourceType> getResourceTypes() {
+        return resourceTypes;
     }
 
     /**
@@ -61,7 +63,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(bundle, resourceType);
+        return Objects.hash(bundle, resourceTypes);
     }
 
     @Override
@@ -71,7 +73,7 @@
         }
         if (obj instanceof TypeProvider) {
             TypeProvider other = (TypeProvider) obj;
-            return Objects.equals(bundle, other.bundle) && Objects.equals(resourceType, other.resourceType);
+            return Objects.equals(bundle, other.bundle) && Objects.equals(resourceTypes, other.resourceTypes);
         }
         return false;
     }
diff --git a/src/test/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceTypeTest.java b/src/test/java/org/apache/sling/scripting/bundle/tracker/ResourceTypeTest.java
similarity index 97%
rename from src/test/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceTypeTest.java
rename to src/test/java/org/apache/sling/scripting/bundle/tracker/ResourceTypeTest.java
index e8a6a6b..8f102e9 100644
--- a/src/test/java/org/apache/sling/scripting/bundle/tracker/internal/ResourceTypeTest.java
+++ b/src/test/java/org/apache/sling/scripting/bundle/tracker/ResourceTypeTest.java
@@ -16,7 +16,7 @@
  ~ specific language governing permissions and limitations
  ~ under the License.
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-package org.apache.sling.scripting.bundle.tracker.internal;
+package org.apache.sling.scripting.bundle.tracker;
 
 import org.apache.commons.lang3.StringUtils;
 import org.junit.Test;