SLING-10804 respect search path when mounting JSON/FileVault XML folders
also ensure getParentResourceType and isResourceType are working in combination with resource providers from mounted folders
diff --git a/core/src/main/java/org/apache/sling/testing/mock/sling/RRMockResourceResolverWrapper.java b/core/src/main/java/org/apache/sling/testing/mock/sling/RRMockResourceResolverWrapper.java
index 6689e5d..895fa09 100644
--- a/core/src/main/java/org/apache/sling/testing/mock/sling/RRMockResourceResolverWrapper.java
+++ b/core/src/main/java/org/apache/sling/testing/mock/sling/RRMockResourceResolverWrapper.java
@@ -18,15 +18,21 @@
  */
 package org.apache.sling.testing.mock.sling;
 
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.wrappers.ResourceResolverWrapper;
+import org.apache.sling.resourceresolver.impl.ResourceTypeUtil;
 import org.apache.sling.spi.resource.provider.ResolveContext;
 import org.apache.sling.spi.resource.provider.ResourceContext;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
@@ -50,16 +56,26 @@
     @Override
     @SuppressWarnings("unchecked")
     public Resource getResource(@NotNull String path) {
-        ResourceProvider resourceProvider = getMatchingResourceProvider(path);
-        if (resourceProvider != null) {
-            return resourceProvider.getResource(this, path, ResourceContext.EMPTY_CONTEXT, null);
+        if (resourceProviders.isEmpty()) {
+            return super.getResource(path);
         }
-        return super.getResource(path);
+        List<String> normalizedPaths = getNormalizedPaths(path);
+        for (String normalizedPath : normalizedPaths) {
+            ResourceProvider resourceProvider = getMatchingResourceProvider(normalizedPath);
+            if (resourceProvider != null) {
+                return resourceProvider.getResource(this, normalizedPath, ResourceContext.EMPTY_CONTEXT, null);
+            }
+            return super.getResource(path);
+        }
+        return null;
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public @NotNull Iterator<Resource> listChildren(@NotNull Resource parent) {
+        if (resourceProviders.isEmpty()) {
+            return super.listChildren(parent);
+        }
         ResourceProvider resourceProvider = getMatchingResourceProvider(parent.getPath());
         if (resourceProvider != null) {
             Iterator<Resource> result = resourceProvider.listChildren(this,  parent);
@@ -74,24 +90,13 @@
     }
 
     private ResourceProvider getMatchingResourceProvider(String path) {
-        if (resourceProviders.isEmpty()) {
-            return null;
-        }
-        String normalizedPath = ResourceUtil.normalize(path);
-        if (!StringUtils.startsWith(normalizedPath, "/")) {
-            return null;
-        }
-        return getMatchingResourceProviderRecursively(normalizedPath);
-    }
-
-    private ResourceProvider getMatchingResourceProviderRecursively(String path) {
         ResourceProvider provider = resourceProviders.get(path);
         if (provider != null) {
             return provider;
         }
         String parentPath = ResourceUtil.getParent(path);
         if (parentPath != null) {
-            return getMatchingResourceProviderRecursively(parentPath);
+            return getMatchingResourceProvider(parentPath);
         }
         else {
             return null;
@@ -118,4 +123,89 @@
         return null;
     }
 
+    private List<String> getNormalizedPaths(String path) {
+        List<String> result = new ArrayList<>();
+        if (StringUtils.startsWith(path, "/")) {
+            result.add(ResourceUtil.normalize(path));
+        }
+        else {
+            for (String searchPath : getSearchPath()) {
+                String combinedPath = StringUtils.removeEnd(searchPath, "/") + "/" + path;
+                result.add(ResourceUtil.normalize(combinedPath));
+            }
+        }
+        return result;
+    }
+
+    // duplicated method from MockResourceResolver to ensure resources from resource providers are respected as well
+    @Override
+    public boolean isResourceType(Resource resource, String resourceType) {
+        if (resourceProviders.isEmpty()) {
+            return super.isResourceType(resource, resourceType);
+        }
+        boolean result = false;
+        if ( resource != null && resourceType != null ) {
+             // Check if the resource is of the given type. This method first checks the
+             // resource type of the resource, then its super resource type and continues
+             //  to go up the resource super type hierarchy.
+             if (ResourceTypeUtil.areResourceTypesEqual(resourceType, resource.getResourceType(), getSearchPath())) {
+                 result = true;
+             } else {
+                 Set<String> superTypesChecked = new HashSet<>();
+                 String superType = this.getParentResourceType(resource);
+                 while (!result && superType != null) {
+                     if (ResourceTypeUtil.areResourceTypesEqual(resourceType, superType, getSearchPath())) {
+                         result = true;
+                     } else {
+                         superTypesChecked.add(superType);
+                         superType = this.getParentResourceType(superType);
+                         if (superType != null && superTypesChecked.contains(superType)) {
+                             throw new SlingException("Cyclic dependency for resourceSuperType hierarchy detected on resource " + resource.getPath()) {
+                                // anonymous class to avoid problem with null cause
+                                private static final long serialVersionUID = 1L;
+                             };
+                         }
+                     }
+                 }
+             }
+
+        }
+        return result;
+    }
+
+    // duplicated method from MockResourceResolver to ensure resources from resource providers are respected as well
+    @Override
+    public String getParentResourceType(Resource resource) {
+        if (resourceProviders.isEmpty()) {
+            return super.getParentResourceType(resource);
+        }
+        String resourceSuperType = null;
+        if ( resource != null ) {
+            resourceSuperType = resource.getResourceSuperType();
+            if (resourceSuperType == null) {
+                resourceSuperType = this.getParentResourceType(resource.getResourceType());
+            }
+        }
+        return resourceSuperType;
+    }
+
+    // duplicated method from MockResourceResolver to ensure resources from resource providers are respected as well
+    @Override
+    public String getParentResourceType(String resourceType) {
+        if (resourceProviders.isEmpty()) {
+            return super.getParentResourceType(resourceType);
+        }
+        // normalize resource type to a path string
+        final String rtPath = (resourceType == null ? null : ResourceUtil.resourceTypeToPath(resourceType));
+        // get the resource type resource and check its super type
+        String resourceSuperType = null;
+        if ( rtPath != null ) {
+            final Resource rtResource = getResource(rtPath);
+            if (rtResource != null) {
+                resourceSuperType = rtResource.getResourceSuperType();
+            }
+        }
+        return resourceSuperType;
+    }
+
 }
diff --git a/core/src/main/java/org/apache/sling/testing/mock/sling/loader/ContentLoader.java b/core/src/main/java/org/apache/sling/testing/mock/sling/loader/ContentLoader.java
index 108892a..de0badf 100644
--- a/core/src/main/java/org/apache/sling/testing/mock/sling/loader/ContentLoader.java
+++ b/core/src/main/java/org/apache/sling/testing/mock/sling/loader/ContentLoader.java
@@ -567,7 +567,7 @@
      * Mount a folder (file system) containing content in FileVault XML format in repository.
      * @param mountFolderPath Root folder path to mount. Path needs to point to the root folder of the content package structure.
      * @param parentResource Parent resource
-     * @param childName Name of child resource to mount folder into
+     * @param childName Name of child resource of subtree path that should be mounted from FileVault XML structure
      */
     public void folderFileVaultXml(@NotNull String mountFolderPath, @NotNull Resource parentResource, @NotNull String childName) {
         folderFileVaultXml(new File(mountFolderPath), parentResource, childName);
@@ -576,7 +576,7 @@
     /**
      * Mount a folder (file system) containing content in FileVault XML format in repository.
      * @param mountFolderPath Root folder path to mount. Path needs to point to the root folder of the content package structure.
-     * @param destPath Path to mount folder into
+     * @param destPath Subtree path that should be mounted from FileVault XML structure
      */
     public void folderFileVaultXml(@NotNull String mountFolderPath, @NotNull String destPath) {
         folderFileVaultXml(new File(mountFolderPath), destPath);
@@ -586,7 +586,7 @@
      * Mount a folder containing content in FileVault XML format in repository.
      * @param mountFolder Root folder to mount. Path needs to point to the root folder of the content package structure.
      * @param parentResource Parent resource
-     * @param childName Name of child resource to mount folder into
+     * @param childName Name of child resource of subtree path that should be mounted from FileVault XML structure
      */
     public void folderFileVaultXml(@NotNull File mountFolder, @NotNull Resource parentResource, @NotNull String childName) {
         folderFileVaultXml(mountFolder, parentResource.getPath() + "/" + childName);
@@ -595,7 +595,7 @@
     /**
      * Mount a folder containing content in FileVault XML format in repository.
      * @param mountFolder Root folder to mount. Path needs to point to the root folder of the content package structure.
-     * @param destPath Path to mount folder into
+     * @param destPath Subtree path that should be mounted from FileVault XML structure
      */
     @SuppressWarnings("null")
     public void folderFileVaultXml(@NotNull File mountFolder, @NotNull String destPath) {
diff --git a/core/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderFolderFileVaultXmlTest.java b/core/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderFolderFileVaultXmlTest.java
index 473ecbf..7942f8b 100644
--- a/core/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderFolderFileVaultXmlTest.java
+++ b/core/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderFolderFileVaultXmlTest.java
@@ -21,12 +21,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.util.JcrConstants;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
@@ -45,12 +47,10 @@
 
     protected abstract ResourceResolverType getResourceResolverType();
 
-    protected String path;
-
     @Before
     public void setUp() {
-        path = "/content";
-        context.load().folderFileVaultXml("src/test/resources/xml-jcr-import-samples", path);
+        context.load().folderFileVaultXml("src/test/resources/xml-jcr-import-samples", "/apps");
+        context.load().folderFileVaultXml("src/test/resources/xml-jcr-import-samples", "/content");
     }
 
     @After
@@ -61,13 +61,13 @@
 
     @Test
     public void testContentResourceType() {
-        Resource resource = context.resourceResolver().getResource(path + "/samples/en/jcr:content");
+        Resource resource = context.resourceResolver().getResource("/content/samples/en/jcr:content");
         assertEquals("samples/sample-app/components/content/page/homepage", resource.getResourceType());
     }
 
     @Test
     public void testContentListChildren() {
-        Resource resource = context.resourceResolver().getResource(path + "/samples/en");
+        Resource resource = context.resourceResolver().getResource("/content/samples/en");
         List<Resource> result = ImmutableList.copyOf(resource.listChildren());
         assertEquals("jcr:content", result.get(0).getName());
         assertEquals("tools", result.get(1).getName());
@@ -75,13 +75,13 @@
 
     @Test
     public void testDamResourceType() {
-        Resource resource = context.resourceResolver().getResource(path + "/dam/talk.png/jcr:content");
+        Resource resource = context.resourceResolver().getResource("/content/dam/talk.png/jcr:content");
         assertEquals("app:AssetContent", resource.getResourceType());
     }
 
     @Test
     public void testBinaryResource() throws IOException {
-        Resource fileResource = context.resourceResolver().getResource(path + "/dam/talk.png/jcr:content/renditions/original");
+        Resource fileResource = context.resourceResolver().getResource("/content/dam/talk.png/jcr:content/renditions/original");
         try (InputStream is = fileResource.adaptTo(InputStream.class)) {
             assertNotNull("InputSteam is null for " + fileResource.getPath(), is);
             byte[] binaryData = IOUtils.toByteArray(is);
@@ -89,4 +89,29 @@
         }
     }
 
+    @Test
+    public void testAppsResource() {
+        Resource resource = context.resourceResolver().getResource("/apps/app1/components/comp1");
+        assertNotNull(resource);
+        assertEquals("Component #1", resource.getValueMap().get(JcrConstants.JCR_TITLE, String.class));
+    }
+
+    @Test
+    public void testAppsResource_SearchPath() {
+        Resource resource = context.resourceResolver().getResource("app1/components/comp1");
+        assertNotNull(resource);
+        assertEquals("Component #1", resource.getValueMap().get(JcrConstants.JCR_TITLE, String.class));
+    }
+
+    @Test
+    public void testAppsResource_ParentResourceType() {
+        Resource resource = context.resourceResolver().getResource("/content/samples/en/jcr:content/comp1-resource");
+        assertNotNull(resource);
+        assertEquals("app1/components/base", context.resourceResolver().getParentResourceType(resource));
+        assertTrue(context.resourceResolver().isResourceType(resource, "app1/components/comp1"));
+        assertTrue(context.resourceResolver().isResourceType(resource, "/apps/app1/components/comp1"));
+        assertTrue(context.resourceResolver().isResourceType(resource, "app1/components/base"));
+        assertTrue(context.resourceResolver().isResourceType(resource, "core/components/superResource"));
+    }
+
 }
diff --git a/core/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderFolderJsonTest.java b/core/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderFolderJsonTest.java
index 712309c..f70549d 100644
--- a/core/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderFolderJsonTest.java
+++ b/core/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderFolderJsonTest.java
@@ -20,10 +20,13 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 import java.util.List;
 
+import org.apache.jackrabbit.vault.util.JcrConstants;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
@@ -42,12 +45,10 @@
 
     protected abstract ResourceResolverType getResourceResolverType();
 
-    protected String path;
-
     @Before
     public void setUp() {
-        path = context.uniqueRoot().content();
-        context.load().folderJson("src/test/resources/json-import-samples", path + "/mount");
+        context.load().folderJson("src/test/resources/json-import-samples", "/mount");
+        context.load().folderJson("src/test/resources/json-import-samples/apps", "/apps");
     }
 
     @After
@@ -58,13 +59,13 @@
 
     @Test
     public void testContentResourceType() {
-        Resource resource = context.resourceResolver().getResource(path + "/mount/content/jcr:content");
+        Resource resource = context.resourceResolver().getResource("/mount/content/jcr:content");
         assertEquals("sample/components/homepage", resource.getResourceType());
     }
 
     @Test
     public void testContentListChildren() {
-        Resource resource = context.resourceResolver().getResource(path + "/mount/content");
+        Resource resource = context.resourceResolver().getResource("/mount/content");
         List<Resource> result = ImmutableList.copyOf(resource.listChildren());
         assertEquals("jcr:content", result.get(0).getName());
         assertEquals("toolbar", result.get(1).getName());
@@ -72,14 +73,39 @@
 
     @Test
     public void testDamResourceType() {
-        Resource resource = context.resourceResolver().getResource(path + "/mount/dam/portraits/scott_reynolds.jpg");
+        Resource resource = context.resourceResolver().getResource("/mount/dam/portraits/scott_reynolds.jpg");
         assertEquals("dam:Asset", resource.getResourceType());
     }
 
     @Test
     public void testBinaryResource() throws IOException {
-        Resource fileResource = context.resourceResolver().getResource(path + "/mount/binary/sample-image.gif");
+        Resource fileResource = context.resourceResolver().getResource("/mount/binary/sample-image.gif");
         AbstractContentLoaderBinaryTest.assertSampleImageFileSize(fileResource);
     }
 
+    @Test
+    public void testAppsResource() {
+        Resource resource = context.resourceResolver().getResource("/apps/app1/components/comp1");
+        assertNotNull(resource);
+        assertEquals("Component #1", resource.getValueMap().get(JcrConstants.JCR_TITLE, String.class));
+    }
+
+    @Test
+    public void testAppsResource_SearchPath() {
+        Resource resource = context.resourceResolver().getResource("app1/components/comp1");
+        assertNotNull(resource);
+        assertEquals("Component #1", resource.getValueMap().get(JcrConstants.JCR_TITLE, String.class));
+    }
+
+    @Test
+    public void testAppsResource_ParentResourceType() {
+        Resource resource = context.resourceResolver().getResource("/mount/content/jcr:content/comp1-resource");
+        assertNotNull(resource);
+        assertEquals("app1/components/base", context.resourceResolver().getParentResourceType(resource));
+        assertTrue(context.resourceResolver().isResourceType(resource, "app1/components/comp1"));
+        assertTrue(context.resourceResolver().isResourceType(resource, "/apps/app1/components/comp1"));
+        assertTrue(context.resourceResolver().isResourceType(resource, "app1/components/base"));
+        assertTrue(context.resourceResolver().isResourceType(resource, "core/components/superResource"));
+    }
+
 }
diff --git a/core/src/test/resources/json-import-samples/apps/app1/components/base.json b/core/src/test/resources/json-import-samples/apps/app1/components/base.json
new file mode 100644
index 0000000..19742d2
--- /dev/null
+++ b/core/src/test/resources/json-import-samples/apps/app1/components/base.json
@@ -0,0 +1,5 @@
+{
+  "jcr:primaryType": "app:Component",
+  "jcr:title": "Base Component",
+  "sling:resourceSuperType": "core/components/superResource"
+}
diff --git a/core/src/test/resources/json-import-samples/apps/app1/components/comp1.json b/core/src/test/resources/json-import-samples/apps/app1/components/comp1.json
new file mode 100644
index 0000000..99705fc
--- /dev/null
+++ b/core/src/test/resources/json-import-samples/apps/app1/components/comp1.json
@@ -0,0 +1,5 @@
+{
+  "jcr:primaryType": "app:Component",
+  "jcr:title": "Component #1",
+  "sling:resourceSuperType": "app1/components/base"
+}
diff --git a/core/src/test/resources/json-import-samples/content.json b/core/src/test/resources/json-import-samples/content.json
index 1c8bccd..c1fa7de 100644
--- a/core/src/test/resources/json-import-samples/content.json
+++ b/core/src/test/resources/json-import-samples/content.json
@@ -15,6 +15,10 @@
     "app:designPath": "/etc/designs/sample",
     "app:lastModifiedBy": "admin",
     "utf8Property": "äöü߀",
+    "comp1-resource": {
+      "jcr:primaryType": "nt:unstructured",
+       "sling:resourceType": "app1/components/comp1"
+    },
     "par": {
       "jcr:primaryType": "nt:unstructured",
       "sling:resourceType": "foundation/components/parsys",
diff --git a/core/src/test/resources/xml-jcr-import-samples/apps/app1/components/base/.content.xml b/core/src/test/resources/xml-jcr-import-samples/apps/app1/components/base/.content.xml
new file mode 100644
index 0000000..ab92315
--- /dev/null
+++ b/core/src/test/resources/xml-jcr-import-samples/apps/app1/components/base/.content.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:app="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+    jcr:primaryType="app:Component"    
+    jcr:title="Base Component" 
+    sling:resourceSuperType="core/components/superResource" />
diff --git a/core/src/test/resources/xml-jcr-import-samples/apps/app1/components/comp1/.content.xml b/core/src/test/resources/xml-jcr-import-samples/apps/app1/components/comp1/.content.xml
new file mode 100644
index 0000000..7ad0240
--- /dev/null
+++ b/core/src/test/resources/xml-jcr-import-samples/apps/app1/components/comp1/.content.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:app="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+    jcr:primaryType="app:Component"    
+    jcr:title="Component #1" 
+    sling:resourceSuperType="app1/components/base" />
diff --git a/core/src/test/resources/xml-jcr-import-samples/content/samples/en/.content.xml b/core/src/test/resources/xml-jcr-import-samples/content/samples/en/.content.xml
index c394cf9..09748dc 100644
--- a/core/src/test/resources/xml-jcr-import-samples/content/samples/en/.content.xml
+++ b/core/src/test/resources/xml-jcr-import-samples/content/samples/en/.content.xml
@@ -34,6 +34,9 @@
       inheritTeaserbar="{Boolean}false"
       navTitle="HOME"
       pageTitle="Sample Site">
+    <comp1-resource
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="app1/components/comp1" />
     <teaserbar
         jcr:primaryType="nt:unstructured"
         sling:resourceType="samples/sample-app/components/content/teaserbar/teaserbarParsys">