You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by an...@apache.org on 2018/07/30 20:21:02 UTC

[sling-org-apache-sling-resourceresolver] branch feature/SLING-7768 updated (511d33d -> f3b9a00)

This is an automated email from the ASF dual-hosted git repository.

andysch pushed a change to branch feature/SLING-7768
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourceresolver.git.


    from 511d33d  Rename the PlaceHolderProvider to StringInterpolationProvider and created a test suite to test the String Interpolation Provider with Map Entries
     new 7e03737  Adding Resource Resolver based path resolution tests and revamping some of the unit tests to consolidate them
     new f3b9a00  SLING-7768: Added /etc/map tests to the resource resolver and extracted some common test code into MockTestUtil class

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../impl/EtcMappingResourceResolverTest.java       | 276 +++++++++++++++++++
 .../impl/MockedResourceResolverImplTest.java       |  21 +-
 .../resourceresolver/impl/SimpleValueMapImpl.java  |   7 +-
 ...est.java => AbstractMappingMapEntriesTest.java} | 149 +++++++----
 .../impl/mapping/EtcMappingMapEntriesTest.java     | 251 ++++++++++++++++++
 .../impl/mapping/MapEntriesTest.java               | 177 ++-----------
 .../mapping/StringInterpolationMapEntriesTest.java | 176 ++----------
 .../sling/resourceresolver/util/MockTestUtil.java  | 294 +++++++++++++++++++++
 8 files changed, 985 insertions(+), 366 deletions(-)
 create mode 100644 src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
 copy src/test/java/org/apache/sling/resourceresolver/impl/mapping/{StringInterpolationMapEntriesTest.java => AbstractMappingMapEntriesTest.java} (60%)
 create mode 100644 src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java
 create mode 100644 src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java


[sling-org-apache-sling-resourceresolver] 02/02: SLING-7768: Added /etc/map tests to the resource resolver and extracted some common test code into MockTestUtil class

Posted by an...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

andysch pushed a commit to branch feature/SLING-7768
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourceresolver.git

commit f3b9a004426c59d32ea669bc177116612bc53379
Author: Andreas Schaefer <sc...@me.com>
AuthorDate: Mon Jul 30 13:20:45 2018 -0700

    SLING-7768: Added /etc/map tests to the resource resolver and extracted some common test code into MockTestUtil class
---
 .../impl/EtcMappingResourceResolverTest.java       | 276 +++++++++++++++++++++
 .../resourceresolver/impl/SimpleValueMapImpl.java  |   7 +-
 .../mapping/AbstractMappingMapEntriesTest.java     |  77 ------
 .../impl/mapping/EtcMappingMapEntriesTest.java     |  11 +-
 .../mapping/StringInterpolationMapEntriesTest.java |   7 +-
 .../sling/resourceresolver/util/MockTestUtil.java  | 198 +++++++++++++--
 6 files changed, 472 insertions(+), 104 deletions(-)

diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
new file mode 100644
index 0000000..b75b456
--- /dev/null
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.resourceresolver.impl;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.path.Path;
+import org.apache.sling.resourceresolver.impl.mapping.MapConfigurationProvider;
+import org.apache.sling.resourceresolver.impl.mapping.MapEntries;
+import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderConfiguration;
+import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderImpl;
+import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler;
+import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
+import org.apache.sling.resourceresolver.impl.providers.ResourceProviderTracker;
+import org.apache.sling.serviceusermapping.ServiceUserMapper;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.event.EventAdmin;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+import static org.apache.sling.resourceresolver.impl.MockedResourceResolverImplTest.createRPHandler;
+import static org.apache.sling.resourceresolver.impl.ResourceResolverImpl.PROP_REDIRECT_INTERNAL;
+import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.ExpectedEtcMapping;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.buildResource;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.callInaccessibleMethod;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.checkInternalResource;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.checkRedirectResource;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.createRequestFromUrl;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.setInaccessibleField;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+/**
+ * These are the same tests as in the EtcMappingMapEntriesTest but in this
+ * class we are actually mocking the Resource Resolver Factory and its classes
+ * and we test the mapping and resource resolution through the resource resolver
+ * rather the MapEntries.
+ */
+public class EtcMappingResourceResolverTest {
+
+    static final String PROP_REG_EXP = "sling:match";
+
+    @Mock
+    ResourceResolverFactory resourceResolverFactory;
+
+    @Mock
+    BundleContext bundleContext;
+
+    @Mock
+    Bundle bundle;
+
+    @Mock
+    EventAdmin eventAdmin;
+
+    @Mock
+    ResourceResolver resourceResolver;
+
+    @Mock
+    ResourceProvider<?> resourceProvider;
+
+    @Mock
+    StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration;
+
+    StringInterpolationProviderImpl stringInterpolationProvider = new StringInterpolationProviderImpl();
+    MapEntries mapEntries;
+
+    File vanityBloomFilterFile;
+
+    CommonResourceResolverFactoryImpl commonFactory;
+
+    Resource etc;
+    Resource map;
+    Resource http;
+
+    Map<String, Map<String, String>> aliasMap;
+
+    @SuppressWarnings({"unchecked"})
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        List<MapConfigurationProvider.VanityPathConfig> configs = getVanityPathConfigs();
+        vanityBloomFilterFile = new File("target/test-classes/resourcesvanityBloomFilter.txt");
+        List<ResourceProviderHandler> handlers = asList(createRPHandler(resourceProvider, "rp1", 0, "/"));
+        ResourceProviderTracker resourceProviderTracker = mock(ResourceProviderTracker.class);
+        ResourceProviderStorage storage = new ResourceProviderStorage(handlers);
+        when(resourceProviderTracker.getResourceProviderStorage()).thenReturn(storage);
+        ResourceResolverFactoryActivator activator = new ResourceResolverFactoryActivator();
+        // These fields on the Activator a package private so we need reflection to access them
+        setInaccessibleField("resourceProviderTracker", activator, resourceProviderTracker);
+        setInaccessibleField("resourceAccessSecurityTracker", activator, new ResourceAccessSecurityTracker());
+        setInaccessibleField("bundleContext", activator, bundleContext);
+        setInaccessibleField("stringInterpolationProvider", activator, stringInterpolationProvider);
+        setInaccessibleField("mapRoot", activator, "/etc/map");
+        setInaccessibleField("mapRootPrefix", activator, "/etc/map");
+        setInaccessibleField("observationPaths", activator, new Path[] {new Path("/")});
+        ServiceUserMapper serviceUserMapper = mock(ServiceUserMapper.class);
+        setInaccessibleField("serviceUserMapper", activator, serviceUserMapper);
+        commonFactory = spy(new CommonResourceResolverFactoryImpl(activator));
+        when(bundleContext.getBundle()).thenReturn(bundle);
+        when(bundleContext.getDataFile("vanityBloomFilter.txt")).thenReturn(vanityBloomFilterFile);
+        when(serviceUserMapper.getServiceUserID(any(Bundle.class),anyString())).thenReturn("mapping");
+        // Activate method is package private so we use reflection to to call it
+        callInaccessibleMethod("activate", commonFactory, BundleContext.class, bundleContext);
+        final Bundle usingBundle = mock(Bundle.class);
+        resourceResolverFactory = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null);
+        resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
+
+        etc = buildResource("/etc", null, resourceResolver, resourceProvider);
+        map = buildResource("/etc/map", etc, resourceResolver, resourceProvider);
+        http = buildResource("/etc/map/http", map, resourceResolver, resourceProvider);
+    }
+
+    List<MapConfigurationProvider.VanityPathConfig> getVanityPathConfigs() {
+        return new ArrayList<>();
+    }
+
+    void refreshMapEntries(String path, boolean isExternal) {
+        ((MapEntries) commonFactory.getMapEntries()).onChange(
+            asList(
+                new ResourceChange(ResourceChange.ChangeType.ADDED, path, isExternal)
+            )
+        );
+    }
+
+    @Test
+    public void root_node_to_content_mapping() throws Exception {
+        buildResource(http.getPath() + "/localhost.8080", http, resourceResolver, resourceProvider, PROP_REDIRECT_EXTERNAL, "/content/simple-node");
+        // This updates the map entries so that the newly added resources are added.
+        // ATTENTION: only call this after all etc-mapping resources are defined as this lock their Resource Meta Data and prevents a re-update
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/simple-node/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for root node to content", commonFactory.getMapEntries().getResolveMaps());
+
+        HttpServletRequest request = createRequestFromUrl("http://localhost:8080/");
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkRedirectResource(resolvedResource, "/content/simple-node/", 302);
+    }
+
+    @Test
+    public void match_to_content_mapping() throws Exception {
+        buildResource("test-node", http, resourceResolver, resourceProvider,
+            PROP_REG_EXP, "localhost.8080/",
+            PROP_REDIRECT_EXTERNAL, "/content/simple-match/"
+        );
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/simple-match/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for match to content", commonFactory.getMapEntries().getResolveMaps());
+
+        HttpServletRequest request = createRequestFromUrl("http://localhost:8080/");
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkRedirectResource(resolvedResource, "/content/simple-match/", 302);
+    }
+
+    // The following tests are based on the example from the https://sling.apache.org/documentation/the-sling-engine/mappings-for-resource-resolution.html page
+
+    @Test
+    public void internal_to_external_node_mapping() throws Exception {
+        buildResource("example.com.80", http, resourceResolver, resourceProvider, PROP_REDIRECT_EXTERNAL, "http://www.example.com/");
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/example.com.80/", "http://www.example.com/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for internal to external based on node", commonFactory.getMapEntries().getResolveMaps());
+
+        HttpServletRequest request = createRequestFromUrl("http://example.com/");
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkRedirectResource(resolvedResource, "http://www.example.com/", 302);
+    }
+
+    @Test
+    public void internal_root_to_content_node_mapping() throws Exception {
+        buildResource("/example", null, resourceResolver, resourceProvider);
+
+        buildResource("www.example.com.80", http, resourceResolver, resourceProvider, PROP_REDIRECT_INTERNAL, "/example");
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping().addEtcMapEntry("^http/www.example.com.80/", true, "/example/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for internal root to content", commonFactory.getMapEntries().getResolveMaps());
+
+        HttpServletRequest request = createRequestFromUrl("http://www.example.com:80/");
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkInternalResource(resolvedResource, "/example");
+    }
+
+    @Test
+    public void host_redirect_match_mapping() throws Exception {
+        buildResource("any_example.com.80", http, resourceResolver, resourceProvider,
+            PROP_REG_EXP, ".+\\.example\\.com\\.80",
+            PROP_REDIRECT_EXTERNAL, "http://www.example.com/"
+        );
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping().addEtcMapEntry("^http/.+\\.example\\.com\\.80", false, "http://www.example.com/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for host redirect match mapping", commonFactory.getMapEntries().getResolveMaps());
+
+        HttpServletRequest request = createRequestFromUrl("http://www.example.com");
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkRedirectResource(resolvedResource, "http://www.example.com//", 302);
+    }
+
+    @Test
+    public void nested_internal_mixed_mapping() throws Exception {
+        Resource localhost = buildResource("localhost_any", http, resourceResolver, resourceProvider,
+            PROP_REG_EXP, "localhost\\.\\d*",
+            PROP_REDIRECT_INTERNAL, "/content"
+        );
+        buildResource("cgi-bin", localhost, resourceResolver, resourceProvider,PROP_REDIRECT_INTERNAL, "/scripts");
+        buildResource("gateway", localhost, resourceResolver, resourceProvider,PROP_REDIRECT_INTERNAL, "http://gbiv.com");
+        buildResource("(stories)", localhost, resourceResolver, resourceProvider,PROP_REDIRECT_INTERNAL, "/anecdotes/$1");
+
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping()
+            .addEtcMapEntry("^http/localhost\\.\\d*", true, "/content")
+            .addEtcMapEntry("^http/localhost\\.\\d*/cgi-bin/", true, "/scripts/")
+            .addEtcMapEntry("^http/localhost\\.\\d*/gateway/", true, "http://gbiv.com/")
+            .addEtcMapEntry("^http/localhost\\.\\d*/(stories)/", true, "/anecdotes/$1/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for nested internal mixed mapping", commonFactory.getMapEntries().getResolveMaps());
+
+        buildResource("/content", null, resourceResolver, resourceProvider);
+        Resource scripts = buildResource("/scripts", null, resourceResolver, resourceProvider);
+        Resource scriptsChild = buildResource("/scripts/child", scripts, resourceResolver, resourceProvider);
+        Resource anecdotes = buildResource("/anecdotes", null, resourceResolver, resourceProvider);
+        Resource stories = buildResource("/anecdotes/stories", anecdotes, resourceResolver, resourceProvider);
+
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getScheme()).thenReturn("http");
+        when(request.getServerName()).thenReturn("localhost");
+        when(request.getServerPort()).thenReturn(1234);
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkInternalResource(resolvedResource, "/content");
+
+        resolvedResource = resourceResolver.resolve(request, "/cgi-bin/");
+        checkInternalResource(resolvedResource, "/scripts");
+        resolvedResource = resourceResolver.resolve(request, "/cgi-bin/child/");
+        checkInternalResource(resolvedResource, "/scripts/child");
+//AS TODO: Does not redirect -> investigate later
+//        resolvedResource = resourceResolver.resolve(request, "/gateway/");
+//        checkRedirectResource(resolvedResource, "http://gbiv.com/", 302);
+        resolvedResource = resourceResolver.resolve(request, "/stories/");
+        checkInternalResource(resolvedResource, "/anecdotes/stories");
+    }
+}
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java b/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java
index f1ed9c1..be498aa 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java
@@ -84,7 +84,12 @@ public class SimpleValueMapImpl implements ValueMap {
     public <T> T get(String name, Class<T> type) {
         Object o = delegate.get(name);
         if ( type.equals(String[].class) && ! ( o instanceof String[])) {
-            o = new String[] { String.valueOf(o) };
+            //AS This works fine unless o is a 'null'. It should return an empty string instead
+            if(o == null) {
+                o = new String[] {};
+            } else {
+                o = new String[]{String.valueOf(o)};
+            }
         }
         return (T) o;
     }
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
index 0f462e0..fdfbfab 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sling.resourceresolver.impl.mapping;
 
-import junit.framework.TestCase;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
@@ -26,7 +25,6 @@ import org.apache.sling.api.resource.path.Path;
 import org.apache.sling.api.wrappers.ValueMapDecorator;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
@@ -38,7 +36,6 @@ import org.osgi.service.event.EventAdmin;
 import java.io.File;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -49,8 +46,6 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.Semaphore;
 
-import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyMap;
@@ -222,78 +217,6 @@ public abstract class AbstractMappingMapEntriesTest {
         }
     }
 
-    static class ExpectedEtcMapping {
-        List<ExpectedEtcMapEntry> expectedEtcMapEntries = new ArrayList<>();
-
-        public ExpectedEtcMapping() {}
-
-        public ExpectedEtcMapping(String...expectedMapping) {
-            if(expectedMapping.length % 2 != 0) {
-                throw new IllegalArgumentException("Expect an even number of strings with pattern / redirect");
-            }
-            int size = expectedMapping.length / 2;
-            for(int i = 0; i < size; i++ ) {
-                expectedEtcMapEntries.add(new ExpectedEtcMapEntry(expectedMapping[2 * i], expectedMapping[2 * i + 1]));
-            }
-        }
-
-        public ExpectedEtcMapping addEtcMapEntry(String pattern, String redirect) {
-            addEtcMapEntry(pattern, false, redirect);
-            return this;
-        }
-        public ExpectedEtcMapping addEtcMapEntry(String pattern, boolean internal, String redirect) {
-            expectedEtcMapEntries.add(new ExpectedEtcMapEntry(pattern, internal, redirect));
-            return this;
-        }
-
-        public void assertEtcMap(String title, List<MapEntry> mapEntries) {
-            assertEquals("Wrong Number of Mappings for: " + title, expectedEtcMapEntries.size(), mapEntries.size());
-            ArrayList<MapEntry> actual = new ArrayList<>(mapEntries);
-            ArrayList<ExpectedEtcMapEntry> expected = new ArrayList<>(expectedEtcMapEntries);
-            for(MapEntry actualMapEntry: actual) {
-                ExpectedEtcMapEntry expectedFound = null;
-                for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) {
-                    if(expectedEtcMapEntry.pattern.equals(actualMapEntry.getPattern())) {
-                        expectedFound = expectedEtcMapEntry;
-                        break;
-                    }
-                }
-                if(expectedFound == null) {
-                    TestCase.fail("This pattern (" + actualMapEntry.getPattern() + ") is not expected for: " + title);
-                }
-                expectedFound.assertEtcMap(title, actualMapEntry);
-                expected.remove(expectedFound);
-            }
-            for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) {
-                TestCase.fail("Expected Map Entry (" + expectedEtcMapEntry.pattern + ") not provided for: " + title);
-            }
-        }
-    }
-
-    static class ExpectedEtcMapEntry {
-        private String pattern;
-        private boolean internal;
-        private String redirect;
-
-        public ExpectedEtcMapEntry(String pattern, String redirect) {
-            this(pattern, false, redirect);
-        }
-
-        public ExpectedEtcMapEntry(String pattern, boolean internal, String redirect) {
-            this.pattern = pattern;
-            this.internal = internal;
-            this.redirect = redirect;
-        }
-
-        public void assertEtcMap(String title, MapEntry mapEntry) {
-            assertEquals("Wrong Pattern for " + title, pattern, mapEntry.getPattern());
-            List<String> givenRedirects = new ArrayList<>(Arrays.asList(mapEntry.getRedirect()));
-            assertEquals("Wrong Number of Redirects for: " + title, 1, givenRedirects.size());
-            assertEquals("Wrong Redirect for: " + title, this.redirect, givenRedirects.get(0));
-            assertEquals("Wrong Redirect Type (ext/int) for: " + title, this.internal, mapEntry.isInternal());
-        }
-    }
-
     /**
      * Iterator to piggyback the list of Resources onto a Resource Mock
      * so that we can add children to them and create the iterators after
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java
index 5526ea0..a98ca6e 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java
@@ -17,7 +17,6 @@
 package org.apache.sling.resourceresolver.impl.mapping;
 
 import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.path.Path;
 import org.apache.sling.resourceresolver.impl.CommonResourceResolverFactoryImpl;
@@ -39,23 +38,23 @@ import javax.servlet.http.HttpServletRequest;
 import java.lang.reflect.Method;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
 import static java.util.Arrays.asList;
 import static org.apache.sling.resourceresolver.impl.MockedResourceResolverImplTest.createRPHandler;
 import static org.apache.sling.resourceresolver.impl.ResourceResolverImpl.PROP_REDIRECT_INTERNAL;
 import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.ExpectedEtcMapping;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyMap;
-import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+/**
+ * These tests are for the /etc/map setup of the Map Entries when
+ * an /etc/map is present.
+ */
 public class EtcMappingMapEntriesTest extends AbstractMappingMapEntriesTest {
 
     @Test
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java
index f1635a0..d500cdc 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java
@@ -19,13 +19,8 @@ package org.apache.sling.resourceresolver.impl.mapping;
 import org.apache.sling.api.resource.Resource;
 import org.junit.Test;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import static junit.framework.TestCase.fail;
 import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
-import static org.junit.Assert.assertEquals;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.ExpectedEtcMapping;
 
 /**
  * These are tests that are testing the Sling Interpolation Feature (SLING-7768)
diff --git a/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java b/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
index 7db3831..4fb1e58 100644
--- a/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
+++ b/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
@@ -16,13 +16,16 @@
  */
 package org.apache.sling.resourceresolver.util;
 
+import junit.framework.TestCase;
+import org.apache.sling.api.resource.NonExistingResource;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.api.wrappers.ValueMapDecorator;
 import org.apache.sling.resourceresolver.impl.SimpleValueMapImpl;
-import org.apache.sling.resourceresolver.impl.mapping.AbstractMappingMapEntriesTest;
+import org.apache.sling.resourceresolver.impl.helper.RedirectResource;
+import org.apache.sling.resourceresolver.impl.mapping.MapEntry;
 import org.apache.sling.spi.resource.provider.ResolveContext;
 import org.apache.sling.spi.resource.provider.ResourceContext;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
@@ -30,39 +33,109 @@ import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
 import static java.util.Arrays.asList;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
 public class MockTestUtil {
 
+    static final String PROP_SLING_TARGET = "sling:target";
+    static final String PROP_SLING_STATUS = "sling:status";
+
+    public static void checkRedirectResource(Resource redirect, String target, int status) {
+        assertThat("Not a Redirect Resource", redirect, instanceOf(RedirectResource.class));
+        RedirectResource redirectResource = (RedirectResource) redirect;
+        ValueMap values = redirectResource.adaptTo(ValueMap.class);
+        assertEquals("Redirect Target is wrong", target, values.get(PROP_SLING_TARGET, String.class));
+        assertEquals("Redirect Status is wrong", new Integer(status), values.get(PROP_SLING_STATUS, Integer.class));
+    }
+
+    public static void checkNonExistingResource(Resource redirect, String path) {
+        assertThat("Not a Non Existing Resource", redirect, instanceOf(NonExistingResource.class));
+        NonExistingResource nonExistingResource = (NonExistingResource) redirect;
+        if(path != null) {
+            assertEquals("Wrong Path for Non Existing Resource", path, nonExistingResource.getPath());
+        }
+    }
+
+    public static void checkInternalResource(Resource internal, String path) {
+//        assertThat("Not a Non Existing Resource", redirect, instanceOf(NonExistingResource.class));
+//        NonExistingResource nonExistingResource = (NonExistingResource) redirect;
+//        if(path != null) {
+        assertEquals("Wrong Path for Resource", path, internal.getPath());
+    }
+
     /**
      * Extract the name from a resource path
+     *
      * @param fullPath Full / Aboslute path to the resource
      * @return Name of the resource
      */
     public static String getResourceName(String fullPath) {
         int n = fullPath.lastIndexOf("/");
-        return fullPath.substring(n+1);
+        return fullPath.substring(n + 1);
+    }
+
+    /**
+     * Creates a Mock Http Servlet Request
+     * @param url Absolute URL to be used to get the method, host and port
+     * @return Http Servlet Request if the url is valid otherwise null
+     */
+    public static HttpServletRequest createRequestFromUrl(String url) {
+        int index = url.indexOf("://");
+        if(index > 0) {
+            String method = url.substring(0, index);
+            int port = 80;
+            int index2 = url.indexOf(":", index + 3);
+            int index3 = url.indexOf("/", index2 > index ? index2 : index + 3);
+            String host = "";
+            if (index2 > 0) {
+                port = new Integer(url.substring(index2 + 1, index3));
+                host = url.substring(index + 3, index2);
+            } else {
+                if(index3 > 0) {
+                    host = url.substring(index + 3, index3);
+                } else {
+                    host = url.substring(index + 3);
+                }
+            }
+            HttpServletRequest request = mock(HttpServletRequest.class);
+            when(request.getScheme()).thenReturn(method);
+            when(request.getServerName()).thenReturn(host);
+            when(request.getServerPort()).thenReturn(port);
+            return request;
+        }
+        return null;
     }
 
     /**
      * Build a resource with path, parent, provider and resource resolver.
-     * @param fullPath Full Path of the Resource
-     * @param parent Parent of this resource but it can be null
+     *
+     * @param fullPath         Full Path of the Resource
+     * @param parent           Parent of this resource but it can be null
      * @param resourceResolver Resource Resolver of this resource
-     * @param provider Resource Provider Instance
-     * @param properties Key / Value pair for resource properties (the number of strings must be even)
+     * @param provider         Resource Provider Instance
+     * @param properties       Key / Value pair for resource properties (the number of strings must be even)
      * @return
      */
     @SuppressWarnings("unchecked")
-    private Resource buildResource(String fullPath, Resource parent, ResourceResolver resourceResolver, ResourceProvider<?> provider, String ... properties) {
-        if(properties != null && properties.length % 2 != 0) { throw new IllegalArgumentException("List of Resource Properties must be an even number: " + asList(properties)); }
+    public static Resource buildResource(String fullPath, Resource parent, ResourceResolver resourceResolver, ResourceProvider<?> provider, String... properties) {
+        if (properties != null && properties.length % 2 != 0) {
+            throw new IllegalArgumentException("List of Resource Properties must be an even number: " + asList(properties));
+        }
         Resource resource = mock(Resource.class, withSettings().name(getResourceName(fullPath)).extraInterfaces(ResourceChildrenAccessor.class));
         when(resource.getName()).thenReturn(getResourceName(fullPath));
         when(resource.getPath()).thenReturn(fullPath);
@@ -70,7 +143,7 @@ public class MockTestUtil {
         when(resource.getResourceMetadata()).thenReturn(resourceMetadata);
         when(resource.getResourceResolver()).thenReturn(resourceResolver);
 
-        if(parent != null) {
+        if (parent != null) {
             List<Resource> childList = ((ResourceChildrenAccessor) parent).getChildrenList();
             childList.add(resource);
         }
@@ -87,7 +160,7 @@ public class MockTestUtil {
         });
 
         // register the resource with the provider
-        if ( provider != null ) {
+        if (provider != null) {
             when(provider.listChildren(Mockito.any(ResolveContext.class), Mockito.eq(resource))).thenAnswer(new Answer<Iterator<Resource>>() {
                 @Override
                 public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable {
@@ -96,11 +169,11 @@ public class MockTestUtil {
             });
             when(provider.getResource(Mockito.any(ResolveContext.class), Mockito.eq(fullPath), Mockito.any(ResourceContext.class), Mockito.any(Resource.class))).thenReturn(resource);
         }
-        if ( properties != null ) {
+        if (properties != null) {
             ValueMap vm = new SimpleValueMapImpl();
-            for ( int i=0; i < properties.length; i+=2) {
-                resourceMetadata.put(properties[i], properties[i+1]);
-                vm.put(properties[i], properties[i+1]);
+            for (int i = 0; i < properties.length; i += 2) {
+                resourceMetadata.put(properties[i], properties[i + 1]);
+                vm.put(properties[i], properties[i + 1]);
             }
             when(resource.getValueMap()).thenReturn(vm);
             when(resource.adaptTo(Mockito.eq(ValueMap.class))).thenReturn(vm);
@@ -112,6 +185,32 @@ public class MockTestUtil {
         return resource;
     }
 
+    public static Object callInaccessibleMethod(String methodName, Object target, Class paramsType, Object param) throws NoSuchMethodException {
+        try {
+            Method method = target.getClass().getDeclaredMethod(methodName, paramsType);
+            method.setAccessible(true);
+            return method.invoke(target, param);
+        } catch(NoSuchMethodException e) {
+            throw new UnsupportedOperationException("Failed to find method: " + methodName, e);
+        } catch (IllegalAccessException e) {
+            throw new UnsupportedOperationException("Failed to access method: " + methodName, e);
+        } catch (InvocationTargetException e) {
+            throw new UnsupportedOperationException("Failed to invoke method: " + methodName, e);
+        }
+    }
+
+    public static void setInaccessibleField(String fieldName, Object target, Object fieldValue) throws NoSuchMethodException {
+        try {
+            Field field = target.getClass().getDeclaredField(fieldName);
+            field.setAccessible(true);
+            field.set(target, fieldValue);
+        } catch (IllegalAccessException e) {
+            throw new UnsupportedOperationException("Failed to access field: " + fieldName, e);
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        }
+    }
+
     /**
      * Iterator to piggyback the list of Resources onto a Resource Mock
      * so that we can add children to them and create the iterators after
@@ -121,4 +220,75 @@ public class MockTestUtil {
         public List<Resource> getChildrenList();
     }
 
+    public static class ExpectedEtcMapping {
+        List<ExpectedEtcMapEntry> expectedEtcMapEntries = new ArrayList<>();
+
+        public ExpectedEtcMapping() {}
+
+        public ExpectedEtcMapping(String...expectedMapping) {
+            if(expectedMapping.length % 2 != 0) {
+                throw new IllegalArgumentException("Expect an even number of strings with pattern / redirect");
+            }
+            int size = expectedMapping.length / 2;
+            for(int i = 0; i < size; i++ ) {
+                expectedEtcMapEntries.add(new ExpectedEtcMapEntry(expectedMapping[2 * i], expectedMapping[2 * i + 1]));
+            }
+        }
+
+        public ExpectedEtcMapping addEtcMapEntry(String pattern, String redirect) {
+            addEtcMapEntry(pattern, false, redirect);
+            return this;
+        }
+        public ExpectedEtcMapping addEtcMapEntry(String pattern, boolean internal, String redirect) {
+            expectedEtcMapEntries.add(new ExpectedEtcMapEntry(pattern, internal, redirect));
+            return this;
+        }
+
+        public void assertEtcMap(String title, List<MapEntry> mapEntries) {
+            assertEquals("Wrong Number of Mappings for: " + title, expectedEtcMapEntries.size(), mapEntries.size());
+            ArrayList<MapEntry> actual = new ArrayList<>(mapEntries);
+            ArrayList<ExpectedEtcMapEntry> expected = new ArrayList<>(expectedEtcMapEntries);
+            for(MapEntry actualMapEntry: actual) {
+                ExpectedEtcMapEntry expectedFound = null;
+                for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) {
+                    if(expectedEtcMapEntry.pattern.equals(actualMapEntry.getPattern())) {
+                        expectedFound = expectedEtcMapEntry;
+                        break;
+                    }
+                }
+                if(expectedFound == null) {
+                    TestCase.fail("This pattern (" + actualMapEntry.getPattern() + ") is not expected for: " + title);
+                }
+                expectedFound.assertEtcMap(title, actualMapEntry);
+                expected.remove(expectedFound);
+            }
+            for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) {
+                TestCase.fail("Expected Map Entry (" + expectedEtcMapEntry.pattern + ") not provided for: " + title);
+            }
+        }
+    }
+
+    public static class ExpectedEtcMapEntry {
+        private String pattern;
+        private boolean internal;
+        private String redirect;
+
+        public ExpectedEtcMapEntry(String pattern, String redirect) {
+            this(pattern, false, redirect);
+        }
+
+        public ExpectedEtcMapEntry(String pattern, boolean internal, String redirect) {
+            this.pattern = pattern;
+            this.internal = internal;
+            this.redirect = redirect;
+        }
+
+        public void assertEtcMap(String title, MapEntry mapEntry) {
+            assertEquals("Wrong Pattern for " + title, pattern, mapEntry.getPattern());
+            List<String> givenRedirects = new ArrayList<>(Arrays.asList(mapEntry.getRedirect()));
+            assertEquals("Wrong Number of Redirects for: " + title, 1, givenRedirects.size());
+            assertEquals("Wrong Redirect for: " + title, this.redirect, givenRedirects.get(0));
+            assertEquals("Wrong Redirect Type (ext/int) for: " + title, this.internal, mapEntry.isInternal());
+        }
+    }
 }


[sling-org-apache-sling-resourceresolver] 01/02: Adding Resource Resolver based path resolution tests and revamping some of the unit tests to consolidate them

Posted by an...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

andysch pushed a commit to branch feature/SLING-7768
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourceresolver.git

commit 7e03737261186acd3d5b381d44543f249ef4af26
Author: Andreas Schaefer <sc...@me.com>
AuthorDate: Fri Jul 27 11:55:51 2018 -0700

    Adding Resource Resolver based path resolution tests and revamping some of the unit tests to consolidate them
---
 .../impl/MockedResourceResolverImplTest.java       |  21 +-
 .../mapping/AbstractMappingMapEntriesTest.java     | 315 +++++++++++++++++++++
 .../impl/mapping/EtcMappingMapEntriesTest.java     | 252 +++++++++++++++++
 .../impl/mapping/MapEntriesTest.java               | 177 ++----------
 .../mapping/StringInterpolationMapEntriesTest.java | 173 ++---------
 .../sling/resourceresolver/util/MockTestUtil.java  | 124 ++++++++
 6 files changed, 754 insertions(+), 308 deletions(-)

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 330b103..d84b714 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java
@@ -17,6 +17,7 @@
  */
 package org.apache.sling.resourceresolver.impl;
 
+import static org.apache.sling.resourceresolver.util.MockTestUtil.getResourceName;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -405,7 +406,7 @@ public class MockedResourceResolverImplTest {
     @SuppressWarnings("unchecked")
     private Resource buildResource(String fullpath, Iterable<Resource> children, ResourceResolver resourceResolver, ResourceProvider<?> provider, String ... properties) {
         Resource resource = Mockito.mock(Resource.class);
-        Mockito.when(resource.getName()).thenReturn(getName(fullpath));
+        Mockito.when(resource.getName()).thenReturn(getResourceName(fullpath));
         Mockito.when(resource.getPath()).thenReturn(fullpath);
         ResourceMetadata resourceMetadata = new ResourceMetadata();
         Mockito.when(resource.getResourceMetadata()).thenReturn(resourceMetadata);
@@ -434,15 +435,15 @@ public class MockedResourceResolverImplTest {
     }
 
 
-    /**
-     * extract the name from a path.
-     * @param fullpath
-     * @return
-     */
-    private String getName(String fullpath) {
-        int n = fullpath.lastIndexOf("/");
-        return fullpath.substring(n+1);
-    }
+//    /**
+//     * extract the name from a path.
+//     * @param fullpath
+//     * @return
+//     */
+//    private String getName(String fullpath) {
+//        int n = fullpath.lastIndexOf("/");
+//        return fullpath.substring(n+1);
+//    }
 
     /**
      * Test getting a resolver.
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
new file mode 100644
index 0000000..0f462e0
--- /dev/null
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
@@ -0,0 +1,315 @@
+/*
+ * 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.resourceresolver.impl.mapping;
+
+import junit.framework.TestCase;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceMetadata;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.resource.path.Path;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.event.EventAdmin;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+
+import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyMap;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+/**
+ * These are tests that are testing the Sling Interpolation Feature (SLING-7768)
+ * on the MapEntries level
+ */
+public abstract class AbstractMappingMapEntriesTest {
+    static final String PROP_REG_EXP = "sling:match";
+
+    @Mock
+    MapConfigurationProvider resourceResolverFactory;
+
+    @Mock
+    BundleContext bundleContext;
+
+    @Mock
+    Bundle bundle;
+
+    @Mock
+    EventAdmin eventAdmin;
+
+    @Mock
+    ResourceResolver resourceResolver;
+
+    @Mock
+    StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration;
+
+    StringInterpolationProviderImpl stringInterpolationProvider = new StringInterpolationProviderImpl();
+    MapEntries mapEntries;
+
+    File vanityBloomFilterFile;
+
+    Resource map;
+    Resource http;
+
+    Map<String, Map<String, String>> aliasMap;
+
+    @SuppressWarnings({"unchecked"})
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        List<MapConfigurationProvider.VanityPathConfig> configs = getVanityPathConfigs();
+        vanityBloomFilterFile = new File("target/test-classes/resourcesvanityBloomFilter.txt");
+        when(bundle.getSymbolicName()).thenReturn("TESTBUNDLE");
+        when(bundleContext.getBundle()).thenReturn(bundle);
+        when(bundleContext.getDataFile("vanityBloomFilter.txt")).thenReturn(vanityBloomFilterFile);
+        when(resourceResolverFactory.getServiceResourceResolver(any(Map.class))).thenReturn(resourceResolver);
+        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);
+        when(resourceResolverFactory.isMaxCachedVanityPathEntriesStartup()).thenReturn(true);
+        when(resourceResolver.findResources(anyString(), eq("sql"))).thenReturn(
+            Collections.<Resource> emptySet().iterator());
+
+        map = setupEtcMapResource("/etc", "map");
+        http = setupEtcMapResource("http", map);
+
+        setupStringInterpolationProvider(new String[] {});
+        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
+
+        final Field aliasMapField = MapEntries.class.getDeclaredField("aliasMap");
+        aliasMapField.setAccessible(true);
+        this.aliasMap = ( Map<String, Map<String, String>>) aliasMapField.get(mapEntries);
+    }
+
+    List<MapConfigurationProvider.VanityPathConfig> getVanityPathConfigs() {
+        return new ArrayList<>();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        vanityBloomFilterFile.delete();
+    }
+
+
+    // -------------------------- private methods ----------
+
+    ValueMap buildValueMap(Object... string) {
+        final Map<String, Object> data = new HashMap<>();
+        for (int i = 0; i < string.length; i = i + 2) {
+            data.put((String) string[i], string[i+1]);
+        }
+        return new ValueMapDecorator(data);
+    }
+
+    Resource getVanityPathResource(final String path) {
+        Resource rsrc = mock(Resource.class);
+        when(rsrc.getPath()).thenReturn(path);
+        when(rsrc.getName()).thenReturn(ResourceUtil.getName(path));
+        when(rsrc.getValueMap()).thenReturn(buildValueMap("sling:vanityPath", "/vanity" + path));
+        return rsrc;
+    }
+
+    Resource setupEtcMapResource(String parentPath, String name, String...valueMapPairs) {
+        return setupEtcMapResource0(parentPath, name, null, valueMapPairs);
+    }
+    Resource setupEtcMapResource(String name, Resource parent, String...valueMapPairs) {
+        return setupEtcMapResource0(null, name, parent, valueMapPairs);
+    }
+    private Resource setupEtcMapResource0(String parentPath, String name, Resource parent, String...valueMapPairs) {
+        Resource resource = mock(Resource.class, withSettings().name(name).extraInterfaces(ResourceDecorator.class));
+        String path = (parent == null ? parentPath : parent.getPath()) + "/" + name;
+        when(resource.getPath()).thenReturn(path);
+        when(resource.getName()).thenReturn(name);
+        ValueMap valueMap = buildValueMap(valueMapPairs);
+        when(resource.getValueMap()).thenReturn(valueMap);
+        when(resource.adaptTo(ValueMap.class)).thenReturn(valueMap);
+        when(resourceResolver.getResource(resource.getPath())).thenReturn(resource);
+        if(parent != null) {
+            List<Resource> childList = ((ResourceDecorator) parent).getChildrenList();
+            childList.add(resource);
+        }
+        final List<Resource> childrenList = new ArrayList<>();
+        when(((ResourceDecorator) resource).getChildrenList()).thenReturn(childrenList);
+        // Delay the children list iterator to make sure all children are added beforehand
+        // Iterators have a modCount that is set when created. Any changes to the underlying list will
+        // change that modCount and the usage of the iterator will fail due to Concurrent Modification Exception
+        when(resource.listChildren()).thenAnswer(new Answer<Iterator<Resource>>() {
+            @Override
+            public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable {
+                return childrenList.iterator();
+            }
+        });
+        ResourceMetadata resourceMetadata = mock(ResourceMetadata.class);
+        when(resource.getResourceMetadata()).thenReturn(resourceMetadata);
+        doNothing().when(resourceMetadata).setResolutionPath(anyString());
+        doNothing().when(resourceMetadata).setParameterMap(anyMap());
+
+        return resource;
+    }
+
+    void setupStringInterpolationProvider(final String[] placeholderValues) {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(placeholderValues);
+        stringInterpolationProvider.activate(bundleContext, stringInterpolationProviderConfiguration);
+    }
+
+    MapEntriesTest.DataFuture createDataFuture(ExecutorService pool, final MapEntries mapEntries) {
+
+        Future<Iterator<?>> future = pool.submit(new Callable<Iterator<?>>() {
+            @Override
+            public Iterator<MapEntry> call() throws Exception {
+                return mapEntries.getResolveMapsIterator("http/localhost.8080/target/justVanityPath");
+            }
+        });
+        return new MapEntriesTest.DataFuture(future);
+    }
+
+    void simulateSomewhatSlowSessionOperation(final Semaphore sessionLock) throws InterruptedException {
+        if (!sessionLock.tryAcquire()) {
+            fail("concurrent session access detected");
+        }
+        try{
+            Thread.sleep(1);
+        } finally {
+            sessionLock.release();
+        }
+    }
+
+    static class ExpectedEtcMapping {
+        List<ExpectedEtcMapEntry> expectedEtcMapEntries = new ArrayList<>();
+
+        public ExpectedEtcMapping() {}
+
+        public ExpectedEtcMapping(String...expectedMapping) {
+            if(expectedMapping.length % 2 != 0) {
+                throw new IllegalArgumentException("Expect an even number of strings with pattern / redirect");
+            }
+            int size = expectedMapping.length / 2;
+            for(int i = 0; i < size; i++ ) {
+                expectedEtcMapEntries.add(new ExpectedEtcMapEntry(expectedMapping[2 * i], expectedMapping[2 * i + 1]));
+            }
+        }
+
+        public ExpectedEtcMapping addEtcMapEntry(String pattern, String redirect) {
+            addEtcMapEntry(pattern, false, redirect);
+            return this;
+        }
+        public ExpectedEtcMapping addEtcMapEntry(String pattern, boolean internal, String redirect) {
+            expectedEtcMapEntries.add(new ExpectedEtcMapEntry(pattern, internal, redirect));
+            return this;
+        }
+
+        public void assertEtcMap(String title, List<MapEntry> mapEntries) {
+            assertEquals("Wrong Number of Mappings for: " + title, expectedEtcMapEntries.size(), mapEntries.size());
+            ArrayList<MapEntry> actual = new ArrayList<>(mapEntries);
+            ArrayList<ExpectedEtcMapEntry> expected = new ArrayList<>(expectedEtcMapEntries);
+            for(MapEntry actualMapEntry: actual) {
+                ExpectedEtcMapEntry expectedFound = null;
+                for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) {
+                    if(expectedEtcMapEntry.pattern.equals(actualMapEntry.getPattern())) {
+                        expectedFound = expectedEtcMapEntry;
+                        break;
+                    }
+                }
+                if(expectedFound == null) {
+                    TestCase.fail("This pattern (" + actualMapEntry.getPattern() + ") is not expected for: " + title);
+                }
+                expectedFound.assertEtcMap(title, actualMapEntry);
+                expected.remove(expectedFound);
+            }
+            for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) {
+                TestCase.fail("Expected Map Entry (" + expectedEtcMapEntry.pattern + ") not provided for: " + title);
+            }
+        }
+    }
+
+    static class ExpectedEtcMapEntry {
+        private String pattern;
+        private boolean internal;
+        private String redirect;
+
+        public ExpectedEtcMapEntry(String pattern, String redirect) {
+            this(pattern, false, redirect);
+        }
+
+        public ExpectedEtcMapEntry(String pattern, boolean internal, String redirect) {
+            this.pattern = pattern;
+            this.internal = internal;
+            this.redirect = redirect;
+        }
+
+        public void assertEtcMap(String title, MapEntry mapEntry) {
+            assertEquals("Wrong Pattern for " + title, pattern, mapEntry.getPattern());
+            List<String> givenRedirects = new ArrayList<>(Arrays.asList(mapEntry.getRedirect()));
+            assertEquals("Wrong Number of Redirects for: " + title, 1, givenRedirects.size());
+            assertEquals("Wrong Redirect for: " + title, this.redirect, givenRedirects.get(0));
+            assertEquals("Wrong Redirect Type (ext/int) for: " + title, this.internal, mapEntry.isInternal());
+        }
+    }
+
+    /**
+     * Iterator to piggyback the list of Resources onto a Resource Mock
+     * so that we can add children to them and create the iterators after
+     * everything is setup
+     */
+    static interface ResourceDecorator {
+        public List<Resource> getChildrenList();
+    }
+
+    static class DataFuture {
+        public Future<Iterator<?>> future;
+
+        public DataFuture(Future<Iterator<?>> future) {
+            super();
+            this.future = future;
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java
new file mode 100644
index 0000000..5526ea0
--- /dev/null
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java
@@ -0,0 +1,252 @@
+/*
+ * 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.resourceresolver.impl.mapping;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceMetadata;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.path.Path;
+import org.apache.sling.resourceresolver.impl.CommonResourceResolverFactoryImpl;
+import org.apache.sling.resourceresolver.impl.ResourceAccessSecurityTracker;
+import org.apache.sling.resourceresolver.impl.ResourceResolverFactoryActivator;
+import org.apache.sling.resourceresolver.impl.ResourceResolverFactoryImpl;
+import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler;
+import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
+import org.apache.sling.resourceresolver.impl.providers.ResourceProviderTracker;
+import org.apache.sling.serviceusermapping.ServiceUserMapper;
+import org.apache.sling.spi.resource.provider.ResolveContext;
+import org.apache.sling.spi.resource.provider.ResourceContext;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+import static org.apache.sling.resourceresolver.impl.MockedResourceResolverImplTest.createRPHandler;
+import static org.apache.sling.resourceresolver.impl.ResourceResolverImpl.PROP_REDIRECT_INTERNAL;
+import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyMap;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+public class EtcMappingMapEntriesTest extends AbstractMappingMapEntriesTest {
+
+    @Test
+    public void root_node_to_content_mapping() throws Exception {
+        setupEtcMapResource("localhost.8080", http,PROP_REDIRECT_EXTERNAL, "/content/simple-node");
+
+        mapEntries.doInit();
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/simple-node/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for simple node", mapEntries.getResolveMaps());
+    }
+
+    @Test
+    public void match_to_content_mapping() throws Exception {
+        setupEtcMapResource("test-node", http,
+            PROP_REG_EXP, "localhost.8080/",
+            PROP_REDIRECT_EXTERNAL, "/content/simple-match/"
+        );
+
+        mapEntries.doInit();
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/simple-match/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for simple match", mapEntries.getResolveMaps());
+    }
+
+    // The following tests are based on the example from the https://sling.apache.org/documentation/the-sling-engine/mappings-for-resource-resolution.html page
+
+    @Test
+    public void internal_to_external_node_mapping() throws Exception {
+        setupEtcMapResource("example.com.80", http,PROP_REDIRECT_EXTERNAL, "http://www.example.com/");
+
+        mapEntries.doInit();
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/example.com.80/", "http://www.example.com/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for internal to external based on node", mapEntries.getResolveMaps());
+    }
+
+    @Test
+    public void internal_root_to_content_node_mapping() throws Exception {
+        setupEtcMapResource("www.example.com.80", http,PROP_REDIRECT_INTERNAL, "/example");
+
+        mapEntries.doInit();
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping().addEtcMapEntry("^http/www.example.com.80/", true, "/example/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for internal root to content", mapEntries.getResolveMaps());
+    }
+
+    @Test
+    public void host_redirect_match_mapping() throws Exception {
+        setupEtcMapResource("any_example.com.80", http,
+            PROP_REG_EXP, ".+\\.example\\.com\\.80",
+            PROP_REDIRECT_EXTERNAL, "http://www.example.com/"
+        );
+
+        mapEntries.doInit();
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping().addEtcMapEntry("^http/.+\\.example\\.com\\.80", false, "http://www.example.com/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for host redirect match mapping", mapEntries.getResolveMaps());
+    }
+
+    @Test
+    public void nested_internal_mixed_mapping() throws Exception {
+        Resource localhost = setupEtcMapResource("localhost_any", http,
+            PROP_REG_EXP, "localhost\\.\\d*",
+            PROP_REDIRECT_INTERNAL, "/content"
+        );
+        setupEtcMapResource("cgi-bin", localhost, PROP_REDIRECT_INTERNAL, "/scripts");
+        setupEtcMapResource("gateway", localhost, PROP_REDIRECT_INTERNAL, "http://gbiv.com");
+        setupEtcMapResource("(stories)", localhost, PROP_REDIRECT_INTERNAL, "/anecdotes/$1");
+
+        mapEntries.doInit();
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping()
+            .addEtcMapEntry("^http/localhost\\.\\d*", true, "/content")
+            .addEtcMapEntry("^http/localhost\\.\\d*/cgi-bin/", true, "/scripts/")
+            .addEtcMapEntry("^http/localhost\\.\\d*/gateway/", true, "http://gbiv.com/")
+            .addEtcMapEntry("^http/localhost\\.\\d*/(stories)/", true, "/anecdotes/$1/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for nested internal mixed mapping", mapEntries.getResolveMaps());
+
+        // Not really an etc-map resource but it is good for now
+        final Resource test = setupEtcMapResource("/scripts", "test");
+        ResourceProvider<?> rp = new ResourceProvider<Object>() {
+            @Override
+            public Resource getResource(ResolveContext<Object> ctx, String path, ResourceContext rCtx, Resource parent) {
+                if(path.equals("/scripts/test")) {
+                    return test;
+                }
+                if(path.startsWith(map.getPath())) {
+                    return findMapping(map, path);
+                }
+                return null;
+            }
+
+            private Resource findMapping(Resource parent, String path) {
+                if(parent.getPath().equals(path)) {
+                    return parent;
+                }
+                Iterator<Resource> i = parent.listChildren();
+                while(i.hasNext()) {
+                    Resource child = i.next();
+                    if(path.equals(child.getPath())) {
+                        return child;
+                    } else {
+                        return findMapping(child, path);
+                    }
+                }
+                return null;
+            }
+
+            @Override
+            public Iterator<Resource> listChildren(ResolveContext<Object> ctx, Resource parent) {
+                if(parent.getPath().startsWith(map.getPath())) {
+                    return parent.listChildren();
+                }
+                return null;
+            }
+        };
+
+        List<ResourceProviderHandler> handlers = asList(createRPHandler(rp, "rp1", 0, "/"));
+        ResourceProviderTracker resourceProviderTracker = mock(ResourceProviderTracker.class);
+        ResourceProviderStorage storage = new ResourceProviderStorage(handlers);
+        when(resourceProviderTracker.getResourceProviderStorage()).thenReturn(storage);
+        ResourceResolverFactoryActivator activator = spy(new ResourceResolverFactoryActivator());
+        // Both 'resourceProviderTracker' and 'resourceAccessSecurityTracker' are package private and so we cannot
+        // set them here. Intercept the call to obtain them and provide the desired value
+        when(activator.getResourceProviderTracker()).thenReturn(resourceProviderTracker);
+        when(activator.getResourceAccessSecurityTracker()).thenReturn(new ResourceAccessSecurityTracker());
+        when(activator.getBundleContext()).thenReturn(bundleContext);
+        when(activator.getStringInterpolationProvider()).thenReturn(stringInterpolationProvider);
+        when(activator.getMapRoot()).thenReturn("/etc/map");
+        when(activator.getObservationPaths()).thenReturn(new Path[] {new Path("/")});
+        CommonResourceResolverFactoryImpl commonFactory = spy(new CommonResourceResolverFactoryImpl(activator));
+        when(bundleContext.getBundle()).thenReturn(bundle);
+        ServiceUserMapper serviceUserMapper = mock(ServiceUserMapper.class);
+        when(activator.getServiceUserMapper()).thenReturn(serviceUserMapper);
+        when(serviceUserMapper.getServiceUserID(any(Bundle.class),anyString())).thenReturn("mapping");
+        Method method = CommonResourceResolverFactoryImpl.class.getDeclaredMethod("activate", BundleContext.class);
+        method.setAccessible(true);
+        method.invoke(commonFactory, bundleContext);
+        final Bundle usingBundle = mock(Bundle.class);
+        ResourceResolverFactoryImpl resFac = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null);
+        ResourceResolver resResolver = resFac.getAdministrativeResourceResolver(null);
+
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getScheme()).thenReturn("http");
+        when(request.getServerName()).thenReturn("localhost");
+        when(request.getServerPort()).thenReturn(80);
+        Resource mappedResource = resResolver.resolve(request, "/cgi-bin/test.html");
+        String path = mappedResource.getPath();
+        assertEquals("Wrong Resolved Path", "/scripts/test", path);
+    }
+
+//    @Test
+//    public void regex_map_internal_mapping() throws Exception {
+//        setupEtcMapResource("regexmap", http,
+//            PROP_REG_EXP, "$1.example.com/$2",
+//            PROP_REDIRECT_INTERNAL, "/content/([^/]+)/(.*)"
+//        );
+//
+//        mapEntries.doInit();
+//        // Regex Mappings are ignored for the Resolve Map
+//        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping();
+////            .addEtcMapEntry("^http/$1.example.com/$2", true, "/content/([^/]+)/(.*)");
+//        expectedEtcMapping.assertEtcMap("Etc Mapping for regex map internal mapping", mapEntries.getResolveMaps());
+//
+//        ResourceProvider<?> rp = new ResourceProvider<Object>() {
+//
+//            @Override
+//            public Resource getResource(ResolveContext<Object> ctx, String path, ResourceContext rCtx, Resource parent) {
+//                return null;
+//            }
+//
+//            @Override
+//            public Iterator<Resource> listChildren(ResolveContext<Object> ctx, Resource parent) {
+//                return null;
+//            }
+//        };
+//
+//        List<ResourceProviderHandler> handlers = asList(createRPHandler(rp, "rp1", 0, "/"));
+//        ResourceProviderTracker resourceProviderTracker = mock(ResourceProviderTracker.class);
+//        ResourceProviderStorage storage = new ResourceProviderStorage(handlers);
+//        when(resourceProviderTracker.getResourceProviderStorage()).thenReturn(storage);
+//        ResourceResolverFactoryActivator activator = spy(new ResourceResolverFactoryActivator());
+//        when(activator.getResourceProviderTracker()).thenReturn(resourceProviderTracker);
+////        activator.resourceProviderTracker = resourceProviderTracker;
+//        when(activator.getResourceAccessSecurityTracker()).thenReturn(new ResourceAccessSecurityTracker());
+////        activator.resourceAccessSecurityTracker = new ResourceAccessSecurityTracker();
+//        CommonResourceResolverFactoryImpl commonFactory = new CommonResourceResolverFactoryImpl(activator);
+//        final Bundle usingBundle = mock(Bundle.class);
+//        ResourceResolverFactoryImpl resFac = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null);
+//        ResourceResolver resResolver = resFac.getAdministrativeResourceResolver(null);
+//
+//        HttpServletRequest request = mock(HttpServletRequest.class);
+//        when(request.getScheme()).thenReturn("http");
+//        when(request.getServerName()).thenReturn("a.example.com");
+//        when(request.getServerPort()).thenReturn(80);
+//        Resource mappedResource = resResolver.resolve(request, "/b.html");
+//        String path = mappedResource.getPath();
+//    }
+}
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 682dacf..b61ac20 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
@@ -16,32 +16,26 @@
  */
 package org.apache.sling.resourceresolver.impl.mapping;
 
-import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-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;
+import org.apache.sling.api.SlingException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.resourceresolver.impl.ResourceResolverImpl;
+import org.apache.sling.resourceresolver.impl.mapping.MapConfigurationProvider.VanityPathConfig;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
-import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.Field;
-import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -49,73 +43,30 @@ import java.util.List;
 import java.util.Map;
 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;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 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.ResourceResolverFactory;
-import org.apache.sling.api.resource.ResourceUtil;
-import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.api.resource.observation.ResourceChange;
-import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
-import org.apache.sling.api.resource.path.Path;
-import org.apache.sling.api.wrappers.ValueMapDecorator;
-import org.apache.sling.resourceresolver.impl.ResourceResolverImpl;
-import org.apache.sling.resourceresolver.impl.mapping.MapConfigurationProvider.VanityPathConfig;
-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;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.service.event.EventAdmin;
-
-public class MapEntriesTest {
-
-    private MapEntries mapEntries;
-
-    File vanityBloomFilterFile;
-
-    @Mock
-    private MapConfigurationProvider resourceResolverFactory;
-
-    @Mock
-    private BundleContext bundleContext;
-
-    @Mock
-    private Bundle bundle;
-
-    @Mock
-    private ResourceResolver resourceResolver;
-
-    @Mock
-    private EventAdmin eventAdmin;
-
-    @Mock
-    private StringInterpolationProvider stringInterpolationProvider;
-
-    private Map<String, Map<String, String>> aliasMap;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+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;
 
-    @SuppressWarnings({ "unchecked" })
-    @Before
-    public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
+public class MapEntriesTest extends AbstractMappingMapEntriesTest {
 
+    List<MapConfigurationProvider.VanityPathConfig> getVanityPathConfigs() {
         final List<VanityPathConfig> configs = new ArrayList<>();
         configs.add(new VanityPathConfig("/libs/", false));
         configs.add(new VanityPathConfig("/libs/denied", true));
@@ -129,35 +80,10 @@ public class MapEntriesTest {
         configs.add(new VanityPathConfig("/vanityPathOnJcrContent", false));
 
         Collections.sort(configs);
-        vanityBloomFilterFile = new File("src/main/resourcesvanityBloomFilter.txt");
-        when(bundle.getSymbolicName()).thenReturn("TESTBUNDLE");
-        when(bundleContext.getBundle()).thenReturn(bundle);
-        when(bundleContext.getDataFile("vanityBloomFilter.txt")).thenReturn(vanityBloomFilterFile);
-        when(resourceResolverFactory.getServiceResourceResolver(any(Map.class))).thenReturn(resourceResolver);
-        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);
-        when(resourceResolverFactory.isMaxCachedVanityPathEntriesStartup()).thenReturn(true);
-        when(resourceResolver.findResources(anyString(), eq("sql"))).thenReturn(
-                Collections.<Resource> emptySet().iterator());
-
-        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
-        final Field aliasMapField = MapEntries.class.getDeclaredField("aliasMap");
-        aliasMapField.setAccessible(true);
-
-        this.aliasMap = ( Map<String, Map<String, String>>) aliasMapField.get(mapEntries);
-    }
 
-    @After
-    public void tearDown() throws Exception {
-        vanityBloomFilterFile.delete();
+        return configs;
     }
 
-
     @Test(timeout = 1000)
     public void test_simple_alias_support() throws InterruptedException {
         Resource parent = mock(Resource.class);
@@ -395,22 +321,6 @@ public class MapEntriesTest {
         assertTrue( mapEntries.getResolveMaps().isEmpty());
     }
 
-    private ValueMap buildValueMap(Object... string) {
-        final Map<String, Object> data = new HashMap<>();
-        for (int i = 0; i < string.length; i = i + 2) {
-            data.put((String) string[i], string[i+1]);
-        }
-        return new ValueMapDecorator(data);
-    }
-
-    private Resource getVanityPathResource(final String path) {
-        Resource rsrc = mock(Resource.class);
-        when(rsrc.getPath()).thenReturn(path);
-        when(rsrc.getName()).thenReturn(ResourceUtil.getName(path));
-        when(rsrc.getValueMap()).thenReturn(buildValueMap("sling:vanityPath", "/vanity" + path));
-        return rsrc;
-    }
-
     @Test
     public void test_vanity_path_registration_include_exclude() throws IOException {
         final String[] validPaths = {"/libs/somewhere", "/libs/a/b", "/foo/a", "/baa/a"};
@@ -2280,39 +2190,4 @@ public class MapEntriesTest {
             }
         }
     }
-
-    // -------------------------- private methods ----------
-    private DataFuture createDataFuture(ExecutorService pool, final MapEntries mapEntries) {
-
-        Future<Iterator<?>> future = pool.submit(new Callable<Iterator<?>>() {
-            @Override
-            public Iterator<MapEntry> call() throws Exception {
-                return mapEntries.getResolveMapsIterator("http/localhost.8080/target/justVanityPath");
-            }
-        });
-        return new DataFuture(future);
-    }
-
-    private void simulateSomewhatSlowSessionOperation(final Semaphore sessionLock) throws InterruptedException {
-        if (!sessionLock.tryAcquire()) {
-            fail("concurrent session access detected");
-        }
-        try{
-            Thread.sleep(1);
-        } finally {
-            sessionLock.release();
-        }
-    }
-
-    // -------------------------- inner classes ------------
-
-    private static class DataFuture {
-        public Future<Iterator<?>> future;
-
-        public DataFuture(Future<Iterator<?>> future) {
-            super();
-            this.future = future;
-        }
-    }
-
 }
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java
index 8392637..f1635a0 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java
@@ -17,101 +17,31 @@
 package org.apache.sling.resourceresolver.impl.mapping;
 
 import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.api.resource.path.Path;
-import org.apache.sling.api.wrappers.ValueMapDecorator;
-import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.osgi.framework.BundleContext;
-import org.osgi.service.event.EventAdmin;
 
-import java.io.File;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
+import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 
+import static junit.framework.TestCase.fail;
 import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.withSettings;
 
 /**
  * These are tests that are testing the Sling Interpolation Feature (SLING-7768)
  * on the MapEntries level
  */
-public class StringInterpolationMapEntriesTest {
-    private static final String PROP_REG_EXP = "sling:match";
-
-    @Mock
-    private MapConfigurationProvider resourceResolverFactory;
-
-    @Mock
-    private BundleContext bundleContext;
-
-    @Mock
-    private EventAdmin eventAdmin;
-
-    @Mock
-    private ResourceResolver resourceResolver;
-
-    @Mock
-    private StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration;
-
-    File vanityBloomFilterFile;
-
-    private Resource map;
-    private Resource http;
-
-    @SuppressWarnings({"unchecked"})
-    @Before
-    public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        when(resourceResolverFactory.getServiceResourceResolver(any(Map.class))).thenReturn(resourceResolver);
-        when(resourceResolverFactory.isVanityPathEnabled()).thenReturn(true);
-        final List<MapConfigurationProvider.VanityPathConfig> configs = new ArrayList<>();
-        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);
-        when(resourceResolverFactory.isMaxCachedVanityPathEntriesStartup()).thenReturn(true);
-        when(resourceResolver.findResources(anyString(), eq("sql"))).thenReturn(
-            Collections.<Resource> emptySet().iterator());
-        vanityBloomFilterFile = new File("target/test-classes/resourcesvanityBloomFilter.txt");
-        when(bundleContext.getDataFile("vanityBloomFilter.txt")).thenReturn(vanityBloomFilterFile);
-
-        map = setupEtcMapResource("/etc", "map");
-        http = setupEtcMapResource("http", map);
-    }
+public class StringInterpolationMapEntriesTest extends AbstractMappingMapEntriesTest {
 
     @Test
     public void simple_node_string_interpolation() throws Exception {
         // To avoid side effects the String Interpolation uses its own Resource Resolver
-        Resource sivOne = setupEtcMapResource("${siv.one}", http,PROP_REDIRECT_EXTERNAL, "/content/test-me");
-        StringInterpolationProvider stringInterpolationProvider = setupStringInterpolationProvider(new String[] {"siv.one=test-value"});
+        Resource sivOne = setupEtcMapResource("${siv.one}", http,PROP_REDIRECT_EXTERNAL, "/content/simple-node");
+        setupStringInterpolationProvider(new String[] {"siv.one=test-simple-node"});
 
-        MapEntries mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
-        List<MapEntry> mapMaps = mapEntries.getResolveMaps();
-        assertEquals("Expected one mapping", 1, mapMaps.size());
-        MapEntry mapEntry = mapMaps.get(0);
-        assertEquals("Wrong String Interpolation for siv.one", "^http/test-value/", mapEntry.getPattern());
-        String[] redirects = mapEntry.getRedirect();
-        assertEquals("Expected one redirect", 1, redirects.length);
-        assertEquals("Wrong Mapping found for siv.one", "/content/test-me/", redirects[0]);
+        mapEntries.doInit();
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-node/", "/content/simple-node/");
+        expectedEtcMapping.assertEtcMap("String Interpolation for simple match", mapEntries.getResolveMaps());
     }
 
     @Test
@@ -119,77 +49,26 @@ public class StringInterpolationMapEntriesTest {
         // To avoid side effects the String Interpolation uses its own Resource Resolver
         Resource sivOne = setupEtcMapResource("test-node", http,
             PROP_REG_EXP, "${siv.one}/",
-            PROP_REDIRECT_EXTERNAL, "/content/test-me/"
+            PROP_REDIRECT_EXTERNAL, "/content/simple-match/"
         );
-        StringInterpolationProvider stringInterpolationProvider = setupStringInterpolationProvider(new String[] {"siv.one=test-value"});
+        setupStringInterpolationProvider(new String[] {"siv.one=test-simple-match"});
 
-        MapEntries mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
-        List<MapEntry> mapMaps = mapEntries.getResolveMaps();
-        assertEquals("Expected one mapping", 1, mapMaps.size());
-        MapEntry mapEntry = mapMaps.get(0);
-        assertEquals("Wrong String Interpolation for siv.one", "^http/test-value/", mapEntry.getPattern());
-        String[] redirects = mapEntry.getRedirect();
-        assertEquals("Expected one redirect", 1, redirects.length);
-        assertEquals("Wrong Mapping found for siv.one", "/content/test-me/", redirects[0]);
+        mapEntries.doInit();
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-match/", "/content/simple-match/");
+        expectedEtcMapping.assertEtcMap("String Interpolation for simple match", mapEntries.getResolveMaps());
     }
 
-    // -------------------------- private methods ----------
-
-    private ValueMap buildValueMap(Object... string) {
-        final Map<String, Object> data = new HashMap<>();
-        for (int i = 0; i < string.length; i = i + 2) {
-            data.put((String) string[i], string[i+1]);
-        }
-        return new ValueMapDecorator(data);
-    }
-
-    private Resource setupEtcMapResource(String parentPath, String name, String...valueMapPairs) {
-        return setupEtcMapResource0(parentPath, name, null, valueMapPairs);
-    }
-    private Resource setupEtcMapResource(String name, Resource parent, String...valueMapPairs) {
-        return setupEtcMapResource0(null, name, parent, valueMapPairs);
-    }
-    private Resource setupEtcMapResource0(String parentPath, String name, Resource parent, String...valueMapPairs) {
-        Resource resource = mock(Resource.class, withSettings().name(name).extraInterfaces(ResourceDecorator.class));
-        String path = (parent == null ? parentPath : parent.getPath()) + "/" + name;
-        when(resource.getPath()).thenReturn(path);
-        when(resource.getName()).thenReturn(name);
-        ValueMap valueMap = buildValueMap(valueMapPairs);
-        when(resource.getValueMap()).thenReturn(valueMap);
-        when(resource.adaptTo(ValueMap.class)).thenReturn(valueMap);
-        when(resourceResolver.getResource(resource.getPath())).thenReturn(resource);
-        if(parent != null) {
-            List<Resource> childList = ((ResourceDecorator) parent).getChildrenList();
-            childList.add(resource);
-        }
-        final List<Resource> childrenList = new ArrayList<>();
-        when(((ResourceDecorator) resource).getChildrenList()).thenReturn(childrenList);
-        // Delay the children list iterator to make sure all children are added beforehand
-        // Iterators have a modCount that is set when created. Any changes to the underlying list will
-        // change that modCount and the usage of the iterator will fail due to Concurrent Modification Exception
-        when(resource.listChildren()).thenAnswer(new Answer<Iterator<Resource>>() {
-            @Override
-            public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable {
-                return childrenList.iterator();
-            }
-        });
-
-        return resource;
-    }
-
-    private StringInterpolationProvider setupStringInterpolationProvider(final String[] placeholderValues) {
-        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(placeholderValues);
-        StringInterpolationProviderImpl stringInterpolationProvider = new StringInterpolationProviderImpl();
-        stringInterpolationProvider.activate(bundleContext, stringInterpolationProviderConfiguration);
-        return stringInterpolationProvider;
-    }
-
-    /**
-     * Iterator to piggyback the list of Resources onto a Resource Mock
-     * so that we can add children to them and create the iterators after
-     * everything is setup
-     */
-    private static interface ResourceDecorator {
-        public List<Resource> getChildrenList();
-    }
+//    @Test
+//    public void simple_nested_match_string_interpolation() throws Exception {
+//        // To avoid side effects the String Interpolation uses its own Resource Resolver
+//        Resource sivOne = setupEtcMapResource("test-node", http,
+//            PROP_REG_EXP, "${siv.one}/",
+//            PROP_REDIRECT_EXTERNAL, "/content/simple-match/"
+//        );
+//        setupStringInterpolationProvider(new String[] {"siv.one=test-simple-match"});
+//
+//        mapEntries.doInit();
+//        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-match/", "/content/simple-match/");
+//        expectedEtcMapping.assertEtcMap("String Interpolation for simple match", mapEntries.getResolveMaps());
+//    }
 }
diff --git a/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java b/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
new file mode 100644
index 0000000..7db3831
--- /dev/null
+++ b/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
@@ -0,0 +1,124 @@
+/*
+ * 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.resourceresolver.util;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceMetadata;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.resourceresolver.impl.SimpleValueMapImpl;
+import org.apache.sling.resourceresolver.impl.mapping.AbstractMappingMapEntriesTest;
+import org.apache.sling.spi.resource.provider.ResolveContext;
+import org.apache.sling.spi.resource.provider.ResourceContext;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+public class MockTestUtil {
+
+    /**
+     * Extract the name from a resource path
+     * @param fullPath Full / Aboslute path to the resource
+     * @return Name of the resource
+     */
+    public static String getResourceName(String fullPath) {
+        int n = fullPath.lastIndexOf("/");
+        return fullPath.substring(n+1);
+    }
+
+    /**
+     * Build a resource with path, parent, provider and resource resolver.
+     * @param fullPath Full Path of the Resource
+     * @param parent Parent of this resource but it can be null
+     * @param resourceResolver Resource Resolver of this resource
+     * @param provider Resource Provider Instance
+     * @param properties Key / Value pair for resource properties (the number of strings must be even)
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    private Resource buildResource(String fullPath, Resource parent, ResourceResolver resourceResolver, ResourceProvider<?> provider, String ... properties) {
+        if(properties != null && properties.length % 2 != 0) { throw new IllegalArgumentException("List of Resource Properties must be an even number: " + asList(properties)); }
+        Resource resource = mock(Resource.class, withSettings().name(getResourceName(fullPath)).extraInterfaces(ResourceChildrenAccessor.class));
+        when(resource.getName()).thenReturn(getResourceName(fullPath));
+        when(resource.getPath()).thenReturn(fullPath);
+        ResourceMetadata resourceMetadata = new ResourceMetadata();
+        when(resource.getResourceMetadata()).thenReturn(resourceMetadata);
+        when(resource.getResourceResolver()).thenReturn(resourceResolver);
+
+        if(parent != null) {
+            List<Resource> childList = ((ResourceChildrenAccessor) parent).getChildrenList();
+            childList.add(resource);
+        }
+        final List<Resource> childrenList = new ArrayList<>();
+        when(((ResourceChildrenAccessor) resource).getChildrenList()).thenReturn(childrenList);
+        // Delay the children list iterator to make sure all children are added beforehand
+        // Iterators have a modCount that is set when created. Any changes to the underlying list will
+        // change that modCount and the usage of the iterator will fail due to Concurrent Modification Exception
+        when(resource.listChildren()).thenAnswer(new Answer<Iterator<Resource>>() {
+            @Override
+            public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable {
+                return childrenList.iterator();
+            }
+        });
+
+        // register the resource with the provider
+        if ( provider != null ) {
+            when(provider.listChildren(Mockito.any(ResolveContext.class), Mockito.eq(resource))).thenAnswer(new Answer<Iterator<Resource>>() {
+                @Override
+                public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable {
+                    return childrenList.iterator();
+                }
+            });
+            when(provider.getResource(Mockito.any(ResolveContext.class), Mockito.eq(fullPath), Mockito.any(ResourceContext.class), Mockito.any(Resource.class))).thenReturn(resource);
+        }
+        if ( properties != null ) {
+            ValueMap vm = new SimpleValueMapImpl();
+            for ( int i=0; i < properties.length; i+=2) {
+                resourceMetadata.put(properties[i], properties[i+1]);
+                vm.put(properties[i], properties[i+1]);
+            }
+            when(resource.getValueMap()).thenReturn(vm);
+            when(resource.adaptTo(Mockito.eq(ValueMap.class))).thenReturn(vm);
+        } else {
+            when(resource.getValueMap()).thenReturn(ValueMapDecorator.EMPTY);
+            when(resource.adaptTo(Mockito.eq(ValueMap.class))).thenReturn(ValueMapDecorator.EMPTY);
+        }
+
+        return resource;
+    }
+
+    /**
+     * Iterator to piggyback the list of Resources onto a Resource Mock
+     * so that we can add children to them and create the iterators after
+     * everything is setup
+     */
+    static interface ResourceChildrenAccessor {
+        public List<Resource> getChildrenList();
+    }
+
+}