SLING-10188 - Reduce the number of service registrations for inheriting BundledScriptServlets
* merge capabilities that provide only extend info with other capabilities with the same
resource type
diff --git a/src/main/java/org/apache/sling/servlets/resolver/internal/bundle/BundledRenderUnitCapabilityImpl.java b/src/main/java/org/apache/sling/servlets/resolver/internal/bundle/BundledRenderUnitCapabilityImpl.java
index 645d83e..b54c47d 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/internal/bundle/BundledRenderUnitCapabilityImpl.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/bundle/BundledRenderUnitCapabilityImpl.java
@@ -26,6 +26,7 @@
import java.util.Objects;
import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.servlets.ServletResolverConstants;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.scripting.spi.bundle.BundledRenderUnitCapability;
@@ -131,6 +132,37 @@
return false;
}
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(BundledRenderUnitCapability.class.getSimpleName()).append("[");
+ if (!resourceTypes.isEmpty()) {
+ sb.append(ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES).append("=").append(resourceTypes);
+ }
+ if (!selectors.isEmpty()) {
+ sb.append("; ").append(ServletResolverConstants.SLING_SERVLET_SELECTORS).append("=").append(selectors);
+ }
+ if (StringUtils.isNotEmpty(extension)) {
+ sb.append("; ").append(ServletResolverConstants.SLING_SERVLET_EXTENSIONS).append("=").append(extension);
+ }
+ if (StringUtils.isNotEmpty(method)) {
+ sb.append("; ").append(ServletResolverConstants.SLING_SERVLET_METHODS).append("=").append(method);
+ }
+ if (StringUtils.isNotEmpty(path)) {
+ sb.append("; ").append(ServletResolverConstants.SLING_SERVLET_PATHS).append("=").append(path);
+ }
+ if (StringUtils.isNotEmpty(extendedResourceType)) {
+ sb.append("; ").append(BundledScriptTracker.AT_EXTENDS).append("=").append(extendedResourceType);
+ }
+ if (StringUtils.isNotEmpty(scriptEngineName)) {
+ sb.append("; ").append(BundledScriptTracker.AT_SCRIPT_ENGINE).append("=").append(scriptEngineName);
+ }
+ if (StringUtils.isNotEmpty(scriptExtension)) {
+ sb.append("; ").append(BundledScriptTracker.AT_SCRIPT_EXTENSION).append("=").append(scriptExtension);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
public static BundledRenderUnitCapability fromBundleCapability(@NotNull BundleCapability capability) {
Map<String, Object> attributes = capability.getAttributes();
Set<ResourceType> resourceTypes = new LinkedHashSet<>();
@@ -156,4 +188,76 @@
(String) attributes.get(BundledScriptTracker.AT_SCRIPT_EXTENSION)
);
}
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private Set<ResourceType> resourceTypes;
+ private String path;
+ private List<String> selectors;
+ private String extension;
+ private String method;
+ private String extendedResourceType;
+ private String scriptEngineName;
+ private String scriptExtension;
+
+ public Builder withResourceTypes(@NotNull Set<ResourceType> resourceTypes) {
+ this.resourceTypes = resourceTypes;
+ return this;
+ }
+
+ public Builder withPath(@Nullable String path) {
+ this.path = path;
+ return this;
+ }
+
+ public Builder withSelectors(@NotNull List<String> selectors) {
+ this.selectors = selectors;
+ return this;
+ }
+
+ public Builder withExtension(@Nullable String extension) {
+ this.extension = extension;
+ return this;
+ }
+
+ public Builder withMethod(@Nullable String method) {
+ this.method = method;
+ return this;
+ }
+
+ public Builder withExtendedResourceType(@Nullable String extendedResourceType) {
+ this.extendedResourceType = extendedResourceType;
+ return this;
+ }
+
+ public Builder withScriptEngineName(@Nullable String scriptEngineName) {
+ this.scriptEngineName = scriptEngineName;
+ return this;
+ }
+
+ public Builder withScriptEngineExtension(@Nullable String scriptExtension) {
+ this.scriptExtension = scriptExtension;
+ return this;
+ }
+
+ public Builder fromCapability(@NotNull BundledRenderUnitCapability capability) {
+ this.extendedResourceType = capability.getExtendedResourceType();
+ this.extension = capability.getExtension();
+ this.method = capability.getMethod();
+ this.path = capability.getPath();
+ this.resourceTypes = capability.getResourceTypes();
+ this.scriptEngineName = capability.getScriptEngineName();
+ this.scriptExtension = capability.getScriptExtension();
+ this.selectors = capability.getSelectors();
+ return this;
+ }
+
+ public BundledRenderUnitCapability build() {
+ return new BundledRenderUnitCapabilityImpl(resourceTypes, path, selectors, extension, method, extendedResourceType,
+ scriptEngineName, scriptExtension);
+ }
+ }
}
diff --git a/src/main/java/org/apache/sling/servlets/resolver/internal/bundle/BundledScriptTracker.java b/src/main/java/org/apache/sling/servlets/resolver/internal/bundle/BundledScriptTracker.java
index 6883088..d785b76 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/internal/bundle/BundledScriptTracker.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/bundle/BundledScriptTracker.java
@@ -29,6 +29,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
+import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -134,13 +135,18 @@
LOGGER.debug("Inspecting bundle {} for {} capability.", bundle.getSymbolicName(), NS_SLING_SERVLET);
List<BundleCapability> capabilities = bundleWiring.getCapabilities(NS_SLING_SERVLET);
Map<BundleCapability, BundledRenderUnitCapability> cache = new HashMap<>();
+ capabilities.forEach(bundleCapability -> {
+ BundledRenderUnitCapability bundledRenderUnitCapability =
+ BundledRenderUnitCapabilityImpl.fromBundleCapability(bundleCapability);
+ cache.put(bundleCapability, bundledRenderUnitCapability);
+ });
Set<TypeProvider> requiresChain = collectRequiresChain(bundleWiring, cache);
if (!capabilities.isEmpty()) {
- List<ServiceRegistration<Servlet>> serviceRegistrations = capabilities.stream().flatMap(cap ->
+ Set<BundledRenderUnitCapability> bundledRenderUnitCapabilities = new HashSet<>(cache.values());
+ bundledRenderUnitCapabilities = reduce(bundledRenderUnitCapabilities);
+ List<ServiceRegistration<Servlet>> serviceRegistrations = bundledRenderUnitCapabilities.stream().flatMap(bundledRenderUnitCapability ->
{
Hashtable<String, Object> properties = new Hashtable<>();
- properties.put(Constants.SERVICE_DESCRIPTION, BundledScriptServlet.class.getName() + cap.getAttributes());
- BundledRenderUnitCapability bundledRenderUnitCapability = cache.computeIfAbsent(cap, BundledRenderUnitCapabilityImpl::fromBundleCapability);
BundledRenderUnit executable = null;
TypeProvider baseTypeProvider = new TypeProviderImpl(bundledRenderUnitCapability, bundle);
LinkedHashSet<TypeProvider> inheritanceChain = new LinkedHashSet<>();
@@ -242,11 +248,13 @@
}
properties.put(ServletResolverConstants.SLING_SERVLET_NAME,
String.format("%s (%s)", BundledScriptServlet.class.getSimpleName(), executablePath));
+ properties.put(Constants.SERVICE_DESCRIPTION,
+ BundledScriptServlet.class.getName() + "{" + bundledRenderUnitCapability + "}");
regs.add(
register(bundle.getBundleContext(), new BundledScriptServlet(inheritanceChain, executable), properties)
);
} else {
- LOGGER.warn(String.format("Unable to locate an executable for capability %s.", cap));
+ LOGGER.warn(String.format("Unable to locate an executable for capability %s.", bundledRenderUnitCapability.toString()));
}
return regs.stream();
@@ -589,4 +597,52 @@
}
return requiresChain;
}
+
+ /**
+ * Given a {@code capabilities} set, this method will merge a capability providing a non-null {@link
+ * BundledRenderUnitCapability#getExtendedResourceType()} and just a resource type information with the other capabilities describing
+ * the same resource type.
+ *
+ * @param capabilities the original capabilities set
+ * @return a new set with merged capabilities or the original set, if no merges had to be performed
+ */
+ private Set<BundledRenderUnitCapability> reduce(Set<BundledRenderUnitCapability> capabilities) {
+ Set<BundledRenderUnitCapability> extenders =
+ capabilities.stream().filter(cap -> cap.getExtendedResourceType() != null && !cap.getResourceTypes().isEmpty() &&
+ cap.getSelectors().isEmpty() && cap.getMethod() == null && cap.getExtension() == null && cap.getScriptEngineName() == null).collect(Collectors.toSet());
+ if (extenders.isEmpty()) {
+ return capabilities;
+ }
+ Set<BundledRenderUnitCapability> originalCapabilities = new HashSet<>(capabilities);
+ Set<BundledRenderUnitCapability> newSet = new HashSet<>();
+ originalCapabilities.removeAll(extenders);
+ if (originalCapabilities.isEmpty()) {
+ return extenders;
+ }
+ Iterator<BundledRenderUnitCapability> extendersIterator = extenders.iterator();
+ while (extendersIterator.hasNext()) {
+ BundledRenderUnitCapability extender = extendersIterator.next();
+ Iterator<BundledRenderUnitCapability> mergeCandidates = originalCapabilities.iterator();
+ boolean processedExtender = false;
+ while (mergeCandidates.hasNext()) {
+ BundledRenderUnitCapability mergeCandidate = mergeCandidates.next();
+ if (extender.getResourceTypes().equals(mergeCandidate.getResourceTypes())) {
+ BundledRenderUnitCapability mergedCapability =
+ BundledRenderUnitCapabilityImpl.builder()
+ .fromCapability(mergeCandidate)
+ .withExtendedResourceType(extender.getExtendedResourceType()).build();
+ newSet.add(mergedCapability);
+ mergeCandidates.remove();
+ processedExtender = true;
+ }
+ }
+ if (processedExtender) {
+ extendersIterator.remove();
+ }
+ }
+ // add extenders for which we couldn't merge their properties
+ newSet.addAll(extenders);
+ newSet.addAll(originalCapabilities);
+ return newSet;
+ }
}