SLING-7544 - Make optimized alias lookup non-blocking
Improvied optimized alias lookup to not block during intialization but
fall back to default and enforce non traversal query for building up
aliasmap.
Closes #5.
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java b/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java
index 8cef27d..5db22d5 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java
@@ -537,4 +537,14 @@
}
}
}
+
+ @Override
+ public boolean isAliasMapInitialized() {
+ return mapEntries.isAliasMapInitialized();
+ }
+
+ @Override
+ public boolean isForceNoAliasTraversal() {
+ return this.activator.isForceNoAliasTraversal();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
index 5984363..ced1fb8 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
@@ -197,6 +197,10 @@
return this.config.resource_resolver_optimize_alias_resolution();
}
+ public boolean isForceNoAliasTraversal() {
+ return this.config.force_no_alias_traversal();
+ }
+
public boolean isLogUnclosedResourceResolvers() {
return this.config.resource_resolver_log_unclosed();
}
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryConfig.java b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryConfig.java
index a0aa53f..2666540 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryConfig.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryConfig.java
@@ -149,6 +149,12 @@
" and on the alias update time if the number of aliases is huge (over 10000).")
boolean resource_resolver_optimize_alias_resolution() default true;
+
+ @AttributeDefinition(name = "Force no traversal for optimized alias lookup",
+ description = "When enabled the lookup of alias map for optimized resolution enforces retry until an index is present"+
+ "and does not traverse repository.")
+ boolean force_no_alias_traversal() default true;
+
@AttributeDefinition(name = "Allowed Vanity Path Location",
description ="This setting can contain a list of path prefixes, e.g. /libs/, /content/. If " +
"such a list is configured, only vanity paths from resources starting with this prefix " +
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java
index 0a53c7c..d9f34af 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java
@@ -474,7 +474,7 @@
while (path != null) {
String alias = null;
if (current != null && !path.endsWith(JCR_CONTENT_LEAF)) {
- if (factory.isOptimizeAliasResolutionEnabled()) {
+ if (factory.isOptimizeAliasResolutionEnabled() && factory.isAliasMapInitialized()) {
logger.debug("map: Optimize Alias Resolution is Enabled");
String parentPath = ResourceUtil.getParent(path);
if (parentPath != null) {
@@ -987,7 +987,7 @@
// we do not have a child with the exact name, so we look for
// a child, whose alias matches the childName
- if (factory.isOptimizeAliasResolutionEnabled()){
+ if (factory.isOptimizeAliasResolutionEnabled() && factory.isAliasMapInitialized()){
logger.debug("getChildInternal: Optimize Alias Resolution is Enabled");
//optimization made in SLING-2521
final Map<String, String> aliases = factory.getMapEntries().getAliasMap(parent.getPath());
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapConfigurationProvider.java b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapConfigurationProvider.java
index aeb437c..82c5827 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapConfigurationProvider.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapConfigurationProvider.java
@@ -54,6 +54,8 @@
boolean isOptimizeAliasResolutionEnabled();
+ boolean isAliasMapInitialized();
+
boolean hasVanityPathPrecedence();
Map<String, Object> getServiceUserAuthenticationInfo(final String subServiceName) throws LoginException;
@@ -78,4 +80,6 @@
* If <code>null</code> is returned, all paths are allowed.
*/
List<VanityPathConfig> getVanityPathConfig();
+
+ boolean isForceNoAliasTraversal();
}
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
index 089c3bf..cfa261a 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
@@ -25,6 +25,7 @@
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
+import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -52,7 +53,9 @@
import javax.servlet.http.HttpServletResponse;
import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.SlingException;
import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.QuerySyntaxException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
@@ -75,9 +78,11 @@
public class MapEntries implements
MapEntriesHandler,
ResourceChangeListener,
- ExternalResourceChangeListener {
+ ExternalResourceChangeListener,
+ AutoCloseable {
private static final String JCR_CONTENT = "jcr:content";
+ private static final int TRAVERSAL_RETRY_INTERVAL = 30000;
private static final String JCR_CONTENT_PREFIX = "jcr:content/";
@@ -108,6 +113,10 @@
private static final String JCR_SYSTEM_PREFIX = "/jcr:system/";
+ final static String ALIAS_QUERY_DEFAULT = "SELECT sling:alias FROM nt:base WHERE sling:alias IS NOT NULL";
+
+ final static String ALIAS_QUERY_NO_TRAVERSAL = ALIAS_QUERY_DEFAULT + " option(traversal fail)";
+
static final String ANY_SCHEME_HOST = "[^/]+/[^/]+";
/** default log */
@@ -127,7 +136,7 @@
private Map <String,List <String>> vanityTargets;
- private Map<String, Map<String, String>> aliasMap;
+ private volatile Map<String, Map<String, String>> aliasMap;
private final ReentrantLock initializing = new ReentrantLock();
@@ -141,6 +150,8 @@
private boolean updateBloomFilterFile = false;
+ private Thread aliasTraversal = null;
+
@SuppressWarnings({ "unchecked" })
public MapEntries(final MapConfigurationProvider factory, final BundleContext bundleContext, final EventAdmin eventAdmin)
throws LoginException, IOException {
@@ -152,7 +163,7 @@
this.resolveMapsMap = Collections.singletonMap(GLOBAL_LIST_KEY, (List<MapEntry>)Collections.EMPTY_LIST);
this.mapMaps = Collections.<MapEntry> emptyList();
this.vanityTargets = Collections.<String,List <String>>emptyMap();
- this.aliasMap = Collections.<String, Map<String, String>>emptyMap();
+ this.aliasMap = Collections.emptyMap();
doInit();
@@ -189,10 +200,15 @@
final Map<String, List<MapEntry>> newResolveMapsMap = new ConcurrentHashMap<>();
+ aliasMap = Collections.emptyMap();
//optimization made in SLING-2521
if (this.factory.isOptimizeAliasResolutionEnabled()) {
- final Map<String, Map<String, String>> aliasMap = this.loadAliases(resolver);
- this.aliasMap = aliasMap;
+ aliasTraversal = new Thread(new Runnable(){
+ public void run() {
+ aliasMap = loadAliases(resolver);
+ }
+ });
+ aliasTraversal.start();
}
this.resolveMapsMap = newResolveMapsMap;
@@ -262,6 +278,10 @@
}
+ protected int getTraversalRetryInterval(){
+ return TRAVERSAL_RETRY_INTERVAL;
+ }
+
private boolean addResource(final String path, final AtomicBoolean resolverRefreshed) {
this.initializing.lock();
@@ -634,6 +654,16 @@
return aliasMap.get(parentPath);
}
+ @Override
+ public boolean isAliasMapInitialized() {
+ // since loading the aliases is equivalent to replacing the empty map
+ // with another instance, and the reference is volatile, it's safe
+ // to equate initialization being done with the empty map being replaced
+ // note that it's not provably safe to check the map size, as we might
+ // enter the scenario where there are no aliases
+ return aliasMap != Collections.<String,Map<String,String>> emptyMap();
+ }
+
/**
* get the MapEnty containing all the nodes having a specific vanityPath
*/
@@ -1027,16 +1057,59 @@
*/
private Map<String, Map<String, String>> loadAliases(final ResourceResolver resolver) {
final Map<String, Map<String, String>> map = new ConcurrentHashMap<>();
- final String queryString = "SELECT sling:alias FROM nt:base WHERE sling:alias IS NOT NULL";
- final Iterator<Resource> i = resolver.findResources(queryString, "sql");
- while (i.hasNext()) {
- final Resource resource = i.next();
- loadAlias(resource, map);
- }
+ String queryString = this.factory.isForceNoAliasTraversal() ? ALIAS_QUERY_NO_TRAVERSAL : ALIAS_QUERY_DEFAULT;
+ while (true){
+ try {
+ final Iterator<Resource> i = resolver.findResources(queryString, "sql");
+ while (i.hasNext()) {
+ final Resource resource = i.next();
+ loadAlias(resource, map);
+ }
+ break;
+ } catch (SlingException e) {
+ Throwable cause = unwrapThrowable(e);
+ if (cause instanceof IllegalArgumentException && ALIAS_QUERY_NO_TRAVERSAL.equals(queryString)) {
+ log.debug(
+ "Expected index not available yet - will retry", e);
+ try {
+ TimeUnit.MILLISECONDS.sleep(getTraversalRetryInterval());
+ } catch (InterruptedException ex) {
+ log.warn("Interrupted while sleeping", ex);
+ }
+ } else if (cause instanceof ParseException) {
+ if (ALIAS_QUERY_NO_TRAVERSAL.equals(queryString)) {
+ log.warn("Traversal fail option set but query not accepted by queryengine, falling back to allowing traversal as queryengine might not support option", e);
+ queryString = ALIAS_QUERY_DEFAULT;
+ } else {
+ log.error("Queryengine couldn't parse query - interrupting loading of aliasmap",e);
+ break;
+ }
+ try {
+ TimeUnit.MILLISECONDS.sleep(getTraversalRetryInterval());
+ } catch (InterruptedException ex) {
+ log.warn("Interrupted while sleeping", ex);
+ }
+
+
+ } else {
+ log.error("QueryEngine not able to process query {} ", queryString, e);
+ break;
+ }
+ }
+ }
return map;
}
/**
+ * Extract root cause of exception
+ * @param e {@code Throwable} to be checked
+ * @return Root {@code Throwable}
+ */
+ private Throwable unwrapThrowable(Throwable e) {
+ return e.getCause() == null ? e : unwrapThrowable(e.getCause());
+ }
+
+ /**
* Load alias given a resource
*/
private boolean loadAlias(final Resource resource, Map<String, Map<String, String>> map) {
@@ -1552,4 +1625,11 @@
}
}
+ @Override
+ public void close() throws Exception {
+ if (aliasTraversal != null) {
+ aliasTraversal.interrupt();
+ }
+ }
+
}
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesHandler.java b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesHandler.java
index df19e5d..e445927 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesHandler.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesHandler.java
@@ -51,6 +51,11 @@
public Map<String, String> getAliasMap(String parentPath) {
return Collections.emptyMap();
}
+
+ @Override
+ public boolean isAliasMapInitialized() {
+ return false;
+ }
};
Map<String, String> getAliasMap(String parentPath);
@@ -67,4 +72,6 @@
* This is for the web console plugin
*/
List<MapEntry> getResolveMaps();
+
+ boolean isAliasMapInitialized();
}
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java
index be2dfd5..330b103 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java
@@ -235,6 +235,12 @@
}
@Override
+ public boolean force_no_alias_traversal() {
+ return true;
+ }
+
+
+ @Override
public String[] resource_resolver_mapping() {
return new String[] { "/:/",
"/content/:/", "/system/docroot/:/", "/content.html-/$" };
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java
index 8864fb8..490196b 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -32,6 +33,7 @@
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -44,6 +46,7 @@
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -53,6 +56,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
+import org.apache.sling.api.SlingException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
@@ -66,6 +70,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -124,6 +129,7 @@
when(resourceResolverFactory.isVanityPathEnabled()).thenReturn(true);
when(resourceResolverFactory.getVanityPathConfig()).thenReturn(configs);
when(resourceResolverFactory.isOptimizeAliasResolutionEnabled()).thenReturn(true);
+ when(resourceResolverFactory.isForceNoAliasTraversal()).thenReturn(true);
when(resourceResolverFactory.getObservationPaths()).thenReturn(new Path[] {new Path("/")});
when(resourceResolverFactory.getMapRoot()).thenReturn(MapEntries.DEFAULT_MAP_ROOT);
when(resourceResolverFactory.getMaxCachedVanityPathEntries()).thenReturn(-1L);
@@ -144,8 +150,8 @@
}
- @Test
- public void test_simple_alias_support() {
+ @Test(timeout = 1000)
+ public void test_simple_alias_support() throws InterruptedException {
Resource parent = mock(Resource.class);
when(parent.getPath()).thenReturn("/parent");
@@ -169,14 +175,19 @@
mapEntries.doInit();
+ // looping check for completion of aliasMap as it is calculated asynchronously
+ while (!mapEntries.isAliasMapInitialized()) {
+ Thread.sleep(10);
+ }
+
Map<String, String> aliasMap = mapEntries.getAliasMap("/parent");
assertNotNull(aliasMap);
assertTrue(aliasMap.containsKey("alias"));
assertEquals("child", aliasMap.get("alias"));
}
- @Test
- public void test_that_duplicate_alias_doesnt_replace_first_alias() {
+ @Test(timeout = 1000)
+ public void test_that_duplicate_alias_doesnt_replace_first_alias() throws InterruptedException {
Resource parent = mock(Resource.class);
when(parent.getPath()).thenReturn("/parent");
@@ -206,6 +217,11 @@
mapEntries.doInit();
+ // looping check for completion of aliasMap as it is calculated asynchronously
+ while (!mapEntries.isAliasMapInitialized()) {
+ Thread.sleep(10);
+ }
+
Map<String, String> aliasMap = mapEntries.getAliasMap("/parent");
assertNotNull(aliasMap);
assertTrue(aliasMap.containsKey("alias"));
@@ -1635,6 +1651,166 @@
assertNull(aliasMapEntry);
}
+ @Test(timeout = 1000)
+ public void test_delayed_optimized_aliaslookup() throws Exception {
+ final Method addResource = MapEntries.class.getDeclaredMethod("addResource", String.class, AtomicBoolean.class);
+ addResource.setAccessible(true);
+
+ mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin);
+
+ Resource parent = mock(Resource.class);
+ when(parent.getPath()).thenReturn("/parent");
+
+ final Resource result = mock(Resource.class);
+ when(resourceResolver.getResource("/parent/child")).thenReturn(result);
+ when(result.getParent()).thenReturn(parent);
+ when(result.getPath()).thenReturn("/parent/child");
+ when(result.getName()).thenReturn("child");
+ when(result.getValueMap()).thenReturn(buildValueMap(ResourceResolverImpl.PROP_ALIAS, "alias"));
+
+ addResource.invoke(mapEntries, "/parent/child", new AtomicBoolean());
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ when(resourceResolver.findResources(anyString(), eq("sql"))).thenAnswer(new Answer<Iterator<Resource>>() {
+
+ @Override
+ public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable {
+ latch.await();
+ if (invocation.getArguments()[0].toString().contains(ResourceResolverImpl.PROP_ALIAS)) {
+ return Arrays.asList(result, result).iterator();
+ } else {
+ return Collections.<Resource> emptySet().iterator();
+ }
+ }
+ });
+
+ mapEntries.doInit();
+
+ assertFalse(mapEntries.isAliasMapInitialized());
+ latch.countDown();
+ // looping check for completion of aliasMap as it is calculated asynchronously
+ while (!mapEntries.isAliasMapInitialized()) {
+ Thread.sleep(10);
+ }
+ assertTrue(mapEntries.isAliasMapInitialized());
+ Map<String, String> aliasMap = mapEntries.getAliasMap("/parent");
+ assertEquals(1, aliasMap.size());
+ assertNotNull(aliasMap);
+ assertEquals(1, aliasMap.size());
+ assertEquals("child", aliasMap.get("alias"));
+ }
+
+ @Test(timeout = 1000)
+ public void test_delayed_optimized_aliaslookup_traversalfail() throws Exception {
+ final Method addResource = MapEntries.class.getDeclaredMethod("addResource", String.class, AtomicBoolean.class);
+ addResource.setAccessible(true);
+
+ mapEntries = Mockito.spy(new MapEntries(resourceResolverFactory, bundleContext, eventAdmin));
+ doReturn(100).when(mapEntries).getTraversalRetryInterval();
+
+ Resource parent = mock(Resource.class);
+ when(parent.getPath()).thenReturn("/parent");
+
+ final Resource result = mock(Resource.class);
+ when(resourceResolver.getResource("/parent/child")).thenReturn(result);
+ when(result.getParent()).thenReturn(parent);
+ when(result.getPath()).thenReturn("/parent/child");
+ when(result.getName()).thenReturn("child");
+ when(result.getValueMap()).thenReturn(buildValueMap(ResourceResolverImpl.PROP_ALIAS, "alias"));
+
+ addResource.invoke(mapEntries, "/parent/child", new AtomicBoolean());
+
+ final CountDownLatch latch = new CountDownLatch(2);
+
+ ArgumentCaptor<String> queryCaptor = ArgumentCaptor.forClass(String.class);
+ when(resourceResolver.findResources(queryCaptor.capture(), eq("sql"))).thenAnswer(new Answer<Iterator<Resource>>() {
+
+ @Override
+ public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable {
+ if (latch.getCount() > 0){
+ latch.countDown();
+ throw new SlingException("Query couldn't be satified due to ", new IllegalArgumentException("Traversal") );
+ } else {
+ if (invocation.getArguments()[0].toString().contains(ResourceResolverImpl.PROP_ALIAS)) {
+ return Arrays.asList(result, result).iterator();
+ } else {
+ return Collections.<Resource> emptySet().iterator();
+ }
+ }
+ }
+ });
+
+ mapEntries.doInit();
+
+ assertFalse(mapEntries.isAliasMapInitialized());
+ latch.countDown();
+ // looping check for completion of aliasMap as it is calculated asynchronously
+ while (!mapEntries.isAliasMapInitialized()) {
+ Thread.sleep(10);
+ }
+ assertTrue(queryCaptor.getValue().contains("traversal fail"));
+ assertTrue(mapEntries.isAliasMapInitialized());
+ Map<String, String> aliasMap = mapEntries.getAliasMap("/parent");
+ assertEquals(1, aliasMap.size());
+ assertNotNull(aliasMap);
+ assertEquals(1, aliasMap.size());
+ assertEquals("child", aliasMap.get("alias"));
+ }
+
+ @Test
+ public void test_aliaslookup_traversalfail_not_supported() throws Exception {
+ final Method addResource = MapEntries.class.getDeclaredMethod("addResource", String.class, AtomicBoolean.class);
+ addResource.setAccessible(true);
+
+ mapEntries = Mockito.spy(new MapEntries(resourceResolverFactory, bundleContext, eventAdmin));
+ doReturn(100).when(mapEntries).getTraversalRetryInterval();
+
+ Resource parent = mock(Resource.class);
+ when(parent.getPath()).thenReturn("/parent");
+
+ final Resource result = mock(Resource.class);
+ when(resourceResolver.getResource("/parent/child")).thenReturn(result);
+ when(result.getParent()).thenReturn(parent);
+ when(result.getPath()).thenReturn("/parent/child");
+ when(result.getName()).thenReturn("child");
+ when(result.getValueMap()).thenReturn(buildValueMap(ResourceResolverImpl.PROP_ALIAS, "alias"));
+
+ addResource.invoke(mapEntries, "/parent/child", new AtomicBoolean());
+
+ ArgumentCaptor<String> queryCaptor = ArgumentCaptor.forClass(String.class);
+ when(resourceResolver.findResources(queryCaptor.capture(), eq("sql"))).thenAnswer(new Answer<Iterator<Resource>>() {
+
+ @Override
+ public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable {
+ if (invocation.getArguments()[0].toString().contains("traversal fail")){
+ throw new SlingException("Query couldn't be satified due to ", new ParseException("Options not known", 0) );
+ } else {
+ if (invocation.getArguments()[0].toString().contains(ResourceResolverImpl.PROP_ALIAS)) {
+ return Arrays.asList(result, result).iterator();
+ } else {
+ return Collections.<Resource> emptySet().iterator();
+ }
+ }
+ }
+ });
+
+ mapEntries.doInit();
+
+ assertFalse(mapEntries.isAliasMapInitialized());
+ // looping check for completion of aliasMap as it is calculated asynchronously
+ while (!mapEntries.isAliasMapInitialized()) {
+ Thread.sleep(10);
+ }
+ assertFalse(queryCaptor.getValue().contains("traversal fail"));
+ assertTrue(mapEntries.isAliasMapInitialized());
+ Map<String, String> aliasMap = mapEntries.getAliasMap("/parent");
+ assertEquals(1, aliasMap.size());
+ assertNotNull(aliasMap);
+ assertEquals(1, aliasMap.size());
+ assertEquals("child", aliasMap.get("alias"));
+ }
+
@Test
public void test_isValidVanityPath() throws Exception {
Method method = MapEntries.class.getDeclaredMethod("isValidVanityPath", String.class);