SLING-7134 - Script execution order is not deterministic on Java 9
* the SlingServletResolver now uses the ScriptEngineManager to help in
picking the most appropriate script to render a resource, based on the available
scripts extensions and the order of the ScriptEngineFactories from the list returned by
ScriptEngineManager#getEngineFactories
* the AbstractResourceCollector will use the ordered list of the ScriptEngineFactories extensions
for sorting the candidate WeightedResources
* added tests for the new resource collection mechanism
diff --git a/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java b/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java
index 4e1e6f4..060bfe6 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java
@@ -45,6 +45,8 @@
import javax.management.NotCompliantMBeanException;
import javax.management.StandardMBean;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptEngineManager;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -188,6 +190,11 @@
@Reference(target="("+ServiceUserMapped.SUBSERVICENAME+"=console)")
private ServiceUserMapped consoleServiceUserMapped;
+ @Reference
+ private ScriptEngineManager scriptEngineManager;
+
+ private List<String> scriptEnginesExtensions = new ArrayList<>();
+
private ResourceResolver sharedScriptResolver;
private final Map<ServiceReference<Servlet>, ServletReg> servletsByReference = new HashMap<>();
@@ -654,7 +661,7 @@
return scriptServlet;
}
- final Collection<Resource> candidates = locationUtil.getServlets(resolver);
+ final Collection<Resource> candidates = locationUtil.getServlets(resolver, scriptEnginesExtensions);
if (LOGGER.isDebugEnabled()) {
if (candidates.isEmpty()) {
@@ -880,6 +887,15 @@
LOGGER.debug("Unable to register mbean");
}
}
+ updateScriptEngineExtensions();
+ }
+
+ private void updateScriptEngineExtensions() {
+ List<String> scriptEnginesExtensions = new ArrayList<>();
+ for (ScriptEngineFactory factory : scriptEngineManager.getEngineFactories()) {
+ scriptEnginesExtensions.addAll(factory.getExtensions());
+ }
+ this.scriptEnginesExtensions = Collections.unmodifiableList(scriptEnginesExtensions);
}
/**
@@ -1099,6 +1115,7 @@
@Override
public void handleEvent(final Event event) {
flushCache();
+ updateScriptEngineExtensions();
}
private void flushCache() {
@@ -1291,7 +1308,7 @@
defaultExtensions,
method,
requestPathInfo.getSelectors());
- servlets = locationUtil.getServlets(resourceResolver);
+ servlets = locationUtil.getServlets(resourceResolver, scriptEnginesExtensions);
}
tr(pw);
tdLabel(pw, "Candidates");
diff --git a/src/main/java/org/apache/sling/servlets/resolver/internal/helper/AbstractResourceCollector.java b/src/main/java/org/apache/sling/servlets/resolver/internal/helper/AbstractResourceCollector.java
index 224c0bd..7a15896 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/internal/helper/AbstractResourceCollector.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/helper/AbstractResourceCollector.java
@@ -18,14 +18,20 @@
*/
package org.apache.sling.servlets.resolver.internal.helper;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
+import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.SyntheticResource;
import org.apache.sling.servlets.resolver.internal.SlingServletResolver;
@@ -64,9 +70,32 @@
this.executionPaths = executionPaths;
}
- public final Collection<Resource> getServlets(final ResourceResolver resolver) {
+ public final Collection<Resource> getServlets(final ResourceResolver resolver, final List<String> scriptExtensions) {
- final SortedSet<Resource> resources = new TreeSet<Resource>();
+ final SortedSet<WeightedResource> resources = new TreeSet<>(new Comparator<WeightedResource>() {
+ @Override
+ public int compare(WeightedResource o1, WeightedResource o2) {
+ if (o1.equals(o2)) {
+ return 0;
+ }
+ int comparisonResult = o1.compareTo(o2);
+ if (comparisonResult == 0) {
+ String o1Extension = getScriptExtension(o1.getName());
+ String o2Extension = getScriptExtension(o2.getName());
+ if (StringUtils.isNotEmpty(o1Extension) && StringUtils.isNotEmpty(o2Extension)) {
+ int o1ExtensionIndex = scriptExtensions.indexOf(o1Extension);
+ int o2ExtensionIndex = scriptExtensions.indexOf(o2Extension);
+ if (o1ExtensionIndex > o2ExtensionIndex) {
+ return -1;
+ } else if (o1ExtensionIndex == o2ExtensionIndex) {
+ return 0;
+ }
+ return 1;
+ }
+ }
+ return comparisonResult;
+ }
+ });
final Iterator<String> locations = new LocationIterator(resourceType, resourceSuperType,
baseResourceType, resolver);
while (locations.hasNext()) {
@@ -85,10 +114,12 @@
getWeightedResources(resources, locationRes);
}
- return resources;
+ List<Resource> result = new ArrayList<>(resources.size());
+ result.addAll(resources);
+ return result;
}
- abstract protected void getWeightedResources(final Set<Resource> resources,
+ abstract protected void getWeightedResources(final Set<WeightedResource> resources,
final Resource location);
/**
@@ -104,8 +135,9 @@
* the name of the resource.
* @param methodPrefixWeight The method/prefix weight assigned to the
* resource according to the resource name.
+ * @param scriptExtensionPriority The priority of the script engine used to run this script
*/
- protected final void addWeightedResource(final Set<Resource> resources,
+ protected final void addWeightedResource(final Set<WeightedResource> resources,
final Resource resource,
final int numSelectors,
final int methodPrefixWeight) {
@@ -225,4 +257,12 @@
return false;
}
+ private String getScriptExtension(String scriptName) {
+ int lastIndexOf = scriptName.lastIndexOf('.');
+ if (lastIndexOf > -1 && lastIndexOf < scriptName.length() - 1) {
+ return scriptName.substring(lastIndexOf + 1);
+ }
+ return null;
+ }
+
}
diff --git a/src/main/java/org/apache/sling/servlets/resolver/internal/helper/NamedScriptResourceCollector.java b/src/main/java/org/apache/sling/servlets/resolver/internal/helper/NamedScriptResourceCollector.java
index bcc163c..10b2c64 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/internal/helper/NamedScriptResourceCollector.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/helper/NamedScriptResourceCollector.java
@@ -84,7 +84,7 @@
}
@Override
- protected void getWeightedResources(final Set<Resource> resources,
+ protected void getWeightedResources(final Set<WeightedResource> resources,
final Resource location) {
final ResourceResolver resolver = location.getResourceResolver();
// if extension is set, we first check for an exact script match
diff --git a/src/main/java/org/apache/sling/servlets/resolver/internal/helper/ResourceCollector.java b/src/main/java/org/apache/sling/servlets/resolver/internal/helper/ResourceCollector.java
index 7a8e6a1..20eacc2 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/internal/helper/ResourceCollector.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/helper/ResourceCollector.java
@@ -183,7 +183,7 @@
}
@Override
- protected void getWeightedResources(final Set<Resource> resources,
+ protected void getWeightedResources(final Set<WeightedResource> resources,
final Resource location) {
final ResourceResolver resolver = location.getResourceResolver();
@@ -283,7 +283,7 @@
private boolean checkScriptName(final String scriptName,
final String selector, final String parentName,
final String suffix, final String htmlSuffix,
- final Set<Resource> resources, final Resource child,
+ final Set<WeightedResource> resources, final Resource child,
final int selIdx) {
if (selector != null && matches(scriptName, selector, suffix)) {
addWeightedResource(resources, child, selIdx + 1,
@@ -333,7 +333,7 @@
&& lenScriptName == (lenName + lenSuffix);
}
- private void addLocationServlet(final Set<Resource> resources,
+ private void addLocationServlet(final Set<WeightedResource> resources,
final Resource location) {
final String path = location.getPath()
+ ServletResourceProviderFactory.SERVLET_PATH_EXTENSION;
diff --git a/src/main/java/org/apache/sling/servlets/resolver/internal/helper/WeightedResource.java b/src/main/java/org/apache/sling/servlets/resolver/internal/helper/WeightedResource.java
index 470593f..ad6244e 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/internal/helper/WeightedResource.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/helper/WeightedResource.java
@@ -131,7 +131,6 @@
return 1;
}
- // extensions are equal, compare ordinal (lower ordinal wins)
- return (ordinal < o.ordinal) ? -1 : 1;
+ return 0;
}
}
diff --git a/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTest.java b/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTest.java
index 25ba33c..2bf82d4 100644
--- a/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTest.java
+++ b/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTest.java
@@ -30,6 +30,7 @@
import java.util.List;
import java.util.Map;
+import javax.script.ScriptEngineManager;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServlet;
@@ -134,6 +135,11 @@
resolverField.setAccessible(true);
resolverField.set(servletResolver, factory);
+ // set script engine manager
+ final Field scriptEngineManagerField = resolverClass.getDeclaredField("scriptEngineManager");
+ scriptEngineManagerField.setAccessible(true);
+ scriptEngineManagerField.set(servletResolver, Mockito.mock(ScriptEngineManager.class));
+
final Bundle bundle = Mockito.mock(Bundle.class);
Mockito.when(bundle.getBundleId()).thenReturn(1L);
diff --git a/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ResourceCollectorTest.java b/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ResourceCollectorTest.java
index f53500e..d0cf55d 100644
--- a/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ResourceCollectorTest.java
+++ b/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ResourceCollectorTest.java
@@ -18,9 +18,12 @@
*/
package org.apache.sling.servlets.resolver.internal.helper;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import org.apache.sling.api.resource.Resource;
@@ -139,6 +142,36 @@
effectiveTest(names, baseIdxs, indices);
}
+ public void testScriptExtensionsPriority() {
+ String[] names = {".servlet", // 0
+ "/" + label + ".esp", // 1
+ "/GET.esp", // 2
+ "/" + label + ".html.esp", // 3
+ "/html.esp", // 4
+ ".esp", // 5
+ "/image.esp", // 6
+ "/print/other.esp", // 7
+ "/print.other.esp", // 8
+ "/print.html.esp", // 9
+ "/print/a4.html.esp", // 10
+ "/print/a4.html.js", // 11
+ "/print/a4.html.html", // 12
+ "/print/a4.html.jsp", // 13
+ "/print", // resource to enable walking the tree
+ "/print", // resource to enable walking the tree
+ };
+
+ int[] baseIdxs = { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1 , 0 , 1, 0};
+ int[] indices = {12, 13, 11, 10, 9, 3, 4, 1, 2, 0};
+
+ effectiveTest(names, baseIdxs, indices, new ArrayList<String>(){{
+ add("esp");
+ add("js");
+ add("jsp");
+ add("html");
+ }});
+ }
+
public void testAnyServlets0() {
// use a request with another request method "ANY"
request.setMethod("ANY");
@@ -256,6 +289,10 @@
}
protected void effectiveTest(String[] names, int[] baseIdxs, int[] indices) {
+ effectiveTest(names, baseIdxs, indices, null);
+ }
+
+ protected void effectiveTest(String[] names, int[] baseIdxs, int[] indices, List<String> scriptEngineExtensions) {
String[] base = { "/apps/" + resourceTypePath,
"/libs/" + resourceTypePath };
@@ -271,7 +308,12 @@
}
ResourceCollector lu = ResourceCollector.create(request, null, new String[] {"html"});
- Collection<Resource> res = lu.getServlets(request.getResourceResolver());
+ Collection<Resource> res;
+ if (scriptEngineExtensions != null) {
+ res = lu.getServlets(request.getResourceResolver(), scriptEngineExtensions);
+ } else {
+ res = lu.getServlets(request.getResourceResolver(), Collections.EMPTY_LIST);
+ }
Iterator<Resource> rIter = res.iterator();
for (int index : indices) {
diff --git a/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ScriptSelectionTest.java b/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ScriptSelectionTest.java
index 6e662c7..d084127 100644
--- a/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ScriptSelectionTest.java
+++ b/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ScriptSelectionTest.java
@@ -19,6 +19,7 @@
package org.apache.sling.servlets.resolver.internal.helper;
import java.util.Collection;
+import java.util.Collections;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.commons.testing.sling.MockResource;
@@ -69,7 +70,7 @@
// Create mock request and get scripts from ResourceCollector
final MockSlingHttpServletRequest req = makeRequest(method, selectors, extension);
final ResourceCollector u = ResourceCollector.create(req, null, new String[] {"html"});
- final Collection<Resource> s = u.getServlets(req.getResourceResolver());
+ final Collection<Resource> s = u.getServlets(req.getResourceResolver(), Collections.EMPTY_LIST);
if(expectedScript == null) {
assertFalse("No script must be found", s.iterator().hasNext());
diff --git a/src/test/java/org/apache/sling/servlets/resolver/internal/helper/WeightedResourceTest.java b/src/test/java/org/apache/sling/servlets/resolver/internal/helper/WeightedResourceTest.java
index 78278e5..85ae4e7 100644
--- a/src/test/java/org/apache/sling/servlets/resolver/internal/helper/WeightedResourceTest.java
+++ b/src/test/java/org/apache/sling/servlets/resolver/internal/helper/WeightedResourceTest.java
@@ -72,15 +72,4 @@
assertTrue(lr2.compareTo(lr1) > 0);
}
- public void testCompareToOrdinal() {
- WeightedResource lr1 = new WeightedResource(0, null, 0, WeightedResource.WEIGHT_NONE);
- WeightedResource lr2 = new WeightedResource(1, null, 0, WeightedResource.WEIGHT_NONE);
-
- // expect the same objects to compare equal
- assertEquals(0, lr1.compareTo(lr1));
- assertEquals(0, lr2.compareTo(lr2));
-
- assertTrue(lr1.compareTo(lr2) < 0);
- assertTrue(lr2.compareTo(lr1) > 0);
- }
}