You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by gh...@apache.org on 2020/09/10 22:13:11 UTC

[sling-org-apache-sling-resourceresolver] branch feature/SLING-9662-Introduce-Resource-Mapping-SPI-v2 created (now de835ae)

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

ghenzler pushed a change to branch feature/SLING-9662-Introduce-Resource-Mapping-SPI-v2
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourceresolver.git.


      at de835ae  SLING-9662 Introduce Resource Mapping SPI

This branch includes the following new commits:

     new de835ae  SLING-9662 Introduce Resource Mapping SPI

The 1 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.



[sling-org-apache-sling-resourceresolver] 01/01: SLING-9662 Introduce Resource Mapping SPI

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

ghenzler pushed a commit to branch feature/SLING-9662-Introduce-Resource-Mapping-SPI-v2
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourceresolver.git

commit de835ae65bc4e8b5b6955ee5e679706e0e0a2adf
Author: georg.henzler <ge...@netcentric.biz>
AuthorDate: Fri Aug 14 07:36:58 2020 +0200

    SLING-9662 Introduce Resource Mapping SPI
---
 pom.xml                                            |   2 +-
 .../impl/CommonResourceResolverFactoryImpl.java    |   9 +-
 .../impl/ResourceResolverFactoryActivator.java     |   9 +
 .../impl/ResourceResolverImpl.java                 |   7 +-
 .../impl/mapping/ResourceMapperImpl.java           |  23 ++-
 .../mappingchain/MappingChainContextInternal.java  |  82 +++++++++
 .../impl/mappingchain/MappingChainResult.java      |  53 ++++++
 .../mappingchain/PathToUriMappingServiceImpl.java  | 193 +++++++++++++++++++++
 .../defaultmappers/BasePathMapper.java             |  83 +++++++++
 .../defaultmappers/EnsureHtmlExtensionMapper.java  |  80 +++++++++
 .../impl/EtcMappingResourceResolverTest.java       |  56 +++---
 .../impl/MockedResourceResolverImplTest.java       |   9 +-
 .../resourceresolver/impl/ProviderHandlerTest.java |   2 +
 .../impl/ResourceDecoratorTestBase.java            |   7 +
 .../impl/ResourceResolverImplTest.java             |   2 +
 .../impl/ResourceResolverMangleNamespacesTest.java |  11 +-
 .../mapping/AbstractMappingMapEntriesTest.java     |  51 +++---
 .../impl/mapping/EtcMappingMapEntriesTest.java     |  38 ++--
 .../impl/mapping/ResourceMapperImplTest.java       |   3 +
 .../sling/resourceresolver/util/MockTestUtil.java  |  48 +++--
 .../util/TestPathToUriMappingService.java          |  65 +++++++
 21 files changed, 732 insertions(+), 101 deletions(-)

diff --git a/pom.xml b/pom.xml
index b056ab6..118a877 100644
--- a/pom.xml
+++ b/pom.xml
@@ -106,7 +106,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.21.0</version>
+            <version>2.23.0-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java b/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java
index b925e65..4e423c2 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java
@@ -31,12 +31,11 @@ import java.util.Stack;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.jetbrains.annotations.NotNull;
-
 import org.apache.commons.collections4.BidiMap;
 import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.mapping.PathToUriMappingService;
 import org.apache.sling.api.resource.path.Path;
 import org.apache.sling.resourceresolver.impl.console.ResourceResolverWebConsolePlugin;
 import org.apache.sling.resourceresolver.impl.helper.ResourceDecoratorTracker;
@@ -48,6 +47,7 @@ import org.apache.sling.resourceresolver.impl.mapping.Mapping;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderTracker;
 import org.apache.sling.serviceusermapping.ServiceUserMapper;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.jetbrains.annotations.NotNull;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
@@ -405,6 +405,11 @@ public class CommonResourceResolverFactoryImpl implements ResourceResolverFactor
         return this.activator.getResourceAccessSecurityTracker();
     }
 
+    /** gets PathToUriMappingServiceImpl */
+    public PathToUriMappingService getPathToUriMappingService() {
+        return this.activator.getPathToUriMappingService();
+    }
+
     @NotNull
     @Override
     public ResourceResolver getServiceResourceResolver(
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
index 646a077..6de579a 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
@@ -34,9 +34,11 @@ import org.apache.commons.collections4.BidiMap;
 import org.apache.commons.collections4.bidimap.TreeBidiMap;
 import org.apache.sling.api.resource.ResourceDecorator;
 import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.mapping.PathToUriMappingService;
 import org.apache.sling.api.resource.path.Path;
 import org.apache.sling.api.resource.runtime.RuntimeService;
 import org.apache.sling.resourceresolver.impl.helper.ResourceDecoratorTracker;
+import org.apache.sling.resourceresolver.impl.mapping.MapEntries;
 import org.apache.sling.resourceresolver.impl.mapping.Mapping;
 import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider;
 import org.apache.sling.resourceresolver.impl.observation.ResourceChangeListenerWhiteboard;
@@ -119,6 +121,9 @@ public class ResourceResolverFactoryActivator {
     @Reference
     ResourceAccessSecurityTracker resourceAccessSecurityTracker;
 
+    @Reference
+    PathToUriMappingService pathToUriMappingService;
+
     volatile ResourceProviderTracker resourceProviderTracker;
 
     volatile ResourceChangeListenerWhiteboard changeListenerWhiteboard;
@@ -153,6 +158,10 @@ public class ResourceResolverFactoryActivator {
         return this.resourceAccessSecurityTracker;
     }
 
+    public PathToUriMappingService getPathToUriMappingService() {
+        return this.pathToUriMappingService;
+    }
+
     public EventAdmin getEventAdmin() {
         return this.eventAdmin;
     }
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java
index 2fddf65..6a565b4 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverImpl.java
@@ -30,11 +30,10 @@ import java.util.Map;
 import java.util.Set;
 import java.util.StringTokenizer;
 
-import org.apache.commons.lang3.StringUtils;
-import org.jetbrains.annotations.Nullable;
 import javax.jcr.Session;
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.adapter.annotations.Adaptable;
 import org.apache.sling.adapter.annotations.Adapter;
 import org.apache.sling.api.SlingException;
@@ -48,6 +47,7 @@ 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.ResourceWrapper;
+import org.apache.sling.api.resource.mapping.PathToUriMappingService;
 import org.apache.sling.api.resource.mapping.ResourceMapper;
 import org.apache.sling.resourceresolver.impl.helper.RedirectResource;
 import org.apache.sling.resourceresolver.impl.helper.ResourceIteratorDecorator;
@@ -62,6 +62,7 @@ import org.apache.sling.resourceresolver.impl.mapping.ResourceMapperImpl;
 import org.apache.sling.resourceresolver.impl.params.ParsedParameters;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorageProvider;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -625,7 +626,7 @@ public class ResourceResolverImpl extends SlingAdaptable implements ResourceReso
         
         if ( type == ResourceMapper.class )
             return (AdapterType) new ResourceMapperImpl(this, factory.getResourceDecoratorTracker(), factory.getMapEntries(), 
-                    factory.isOptimizeAliasResolutionEnabled(), factory.getNamespaceMangler());
+                    factory.isOptimizeAliasResolutionEnabled(), factory.getNamespaceMangler(), factory.getPathToUriMappingService());
         
         final AdapterType result = this.control.adaptTo(this.context, type);
         if ( result != null ) {
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImpl.java b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImpl.java
index bc47c26..91cd333 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImpl.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImpl.java
@@ -33,7 +33,9 @@ import javax.servlet.http.HttpServletRequest;
 
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.mapping.PathToUriMappingService;
 import org.apache.sling.api.resource.mapping.ResourceMapper;
+import org.apache.sling.api.resource.uri.ResourceUri;
 import org.apache.sling.resourceresolver.impl.JcrNamespaceMangler;
 import org.apache.sling.resourceresolver.impl.ResourceResolverImpl;
 import org.apache.sling.resourceresolver.impl.helper.ResourceDecoratorTracker;
@@ -53,15 +55,17 @@ public class ResourceMapperImpl implements ResourceMapper {
     private final MapEntriesHandler mapEntries;
     private final boolean optimizedAliasResolutionEnabled;
     private final Object namespaceMangler;
-    
+    private final PathToUriMappingService pathToUriMappingService;
 
     public ResourceMapperImpl(ResourceResolverImpl resolver, ResourceDecoratorTracker resourceDecorator, 
-            MapEntriesHandler mapEntries, boolean optimizedAliasResolutionEnabled, Object namespaceMangler) {
+            MapEntriesHandler mapEntries, boolean optimizedAliasResolutionEnabled, Object namespaceMangler,
+            PathToUriMappingService pathToUriMappingService) {
         this.resolver = resolver;
         this.resourceDecorator = resourceDecorator;
         this.mapEntries = mapEntries;
         this.optimizedAliasResolutionEnabled = optimizedAliasResolutionEnabled;
         this.namespaceMangler = namespaceMangler;
+        this.pathToUriMappingService = pathToUriMappingService;
     }
 
     @Override
@@ -166,6 +170,13 @@ public class ResourceMapperImpl implements ResourceMapper {
         // vanity paths are prepended to make sure they get returned last
         mappings.addAll(0, vanityPaths);
 
+
+        // Apply mappings from Resource Mappers
+        PathToUriMappingService.Result mappingResult = pathToUriMappingService.map(request, mappings.get(mappings.size() - 1));
+        for (Map.Entry<String, ResourceUri> mappingFromChain : mappingResult.getIntermediateMappings().entrySet()) {
+            mappings.add(mappingFromChain.getValue().toString());
+        }
+
         // 7. apply context path if needed
         mappings.replaceAll(new ApplyContextPath(request));
        
@@ -174,10 +185,10 @@ public class ResourceMapperImpl implements ResourceMapper {
             mappings.replaceAll(path -> path.concat(fragmentQuery));
         }
 
-        mappings.forEach( path ->
-            logger.debug("map: Returning URL {} as mapping for path {}", path, resourcePath)    
-        );
-        
+        mappings.forEach( path -> {
+            logger.debug("map: Returning URL {} as mapping for path {}", path, resourcePath);
+        });
+
         Collections.reverse(mappings);
         
         return new LinkedHashSet<>(mappings);
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/MappingChainContextInternal.java b/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/MappingChainContextInternal.java
new file mode 100644
index 0000000..3051ef0
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/MappingChainContextInternal.java
@@ -0,0 +1,82 @@
+/*
+ * 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.mappingchain;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.mapping.PathToUriMappingService.ContextHint;
+import org.apache.sling.api.resource.uri.ResourceUri;
+import org.apache.sling.spi.resource.mapping.MappingChainContext;
+
+class MappingChainContextInternal implements MappingChainContext {
+
+    private final ResourceResolver resourceResolver;
+    private final Map<String, Object> attributes = new HashMap<>();
+    private final Map<String, ResourceUri> intermediateMappings = new LinkedHashMap<>();
+    private boolean skipRemainingChain = false;
+    private final Set<ContextHint> contextHints = new HashSet<>();
+
+    MappingChainContextInternal(ResourceResolver resourceResolver) {
+        this.resourceResolver = resourceResolver;
+    }
+
+    @Override
+    public void skipRemainingChain() {
+        skipRemainingChain = true;
+    }
+
+    @Override
+    public void addContextHint(ContextHint contextHint) {
+        contextHints.add(contextHint);
+    }
+
+    @Override
+    public Map<String, Object> getAttributes() {
+        return attributes;
+    }
+
+    @Override
+    public Map<String, ResourceUri> getIntermediateMappings() {
+        return Collections.unmodifiableMap(intermediateMappings);
+    }
+
+    @Override
+    public ResourceResolver getResourceResolver() {
+        return resourceResolver;
+    }
+
+    boolean isMarkedAsSkipRemainingChain() {
+        return skipRemainingChain;
+    }
+
+    void addIntermediateMapping(String name, ResourceUri resourceUri) {
+        intermediateMappings.put(name, resourceUri);
+    }
+
+    Set<ContextHint> getContextHints() {
+        return contextHints;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/MappingChainResult.java b/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/MappingChainResult.java
new file mode 100644
index 0000000..8c76f07
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/MappingChainResult.java
@@ -0,0 +1,53 @@
+/*
+ * 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.mappingchain;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.api.resource.mapping.PathToUriMappingService;
+import org.apache.sling.api.resource.mapping.PathToUriMappingService.ContextHint;
+import org.apache.sling.api.resource.uri.ResourceUri;
+import org.jetbrains.annotations.NotNull;
+
+class MappingChainResult implements PathToUriMappingService.Result {
+
+    private final ResourceUri resourceUri;
+    private final Set<ContextHint> contextHints;
+    private final Map<String, ResourceUri> intermediateMappings;
+
+    public MappingChainResult(ResourceUri resourceUri, Set<ContextHint> contextHints, Map<String, ResourceUri> intermediateMappings) {
+        this.resourceUri = resourceUri;
+        this.contextHints = contextHints;
+        this.intermediateMappings = intermediateMappings;
+    }
+
+    public ResourceUri getResourceUri() {
+        return resourceUri;
+    }
+
+    public Map<String, ResourceUri> getIntermediateMappings() {
+        return intermediateMappings;
+    }
+
+    @Override
+    public @NotNull Set<ContextHint> getContextHints() {
+        return contextHints;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/PathToUriMappingServiceImpl.java b/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/PathToUriMappingServiceImpl.java
new file mode 100644
index 0000000..4fe3e12
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/PathToUriMappingServiceImpl.java
@@ -0,0 +1,193 @@
+/*
+ * 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.mappingchain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.mapping.PathToUriMappingService;
+import org.apache.sling.api.resource.uri.ResourceUri;
+import org.apache.sling.api.resource.uri.ResourceUriBuilder;
+import org.apache.sling.spi.resource.mapping.ResourceUriMapper;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+public class PathToUriMappingServiceImpl implements PathToUriMappingService {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PathToUriMappingServiceImpl.class);
+
+    private static final String KEY_INTIAL = "intial";
+    private static final String SUBSERVICE_NAME_MAPPING = "mapping";
+
+    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
+    volatile List<ResourceUriMapper> resourceMappers;
+
+    @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
+    volatile ResourceResolverFactory resourceResolverFactory;
+
+    @Activate
+    public void activate() {
+
+        if (resourceMappers != null) {
+            String resourceMappersStr = resourceMappers.stream().map(rm -> rm.getClass().toString()).collect(Collectors.joining("\n"));
+            LOG.info("Available Resource Mappers:\n{}", resourceMappersStr);
+        } else {
+            LOG.info("No Resource Mappers available");
+        }
+    }
+
+    public List<String> getAvailableResourceMappers() {
+        return resourceMappers.stream().map(rm -> rm.getClass().toString()).collect(Collectors.toList());
+    }
+
+    @NotNull
+    public MappingChainResult map(@Nullable HttpServletRequest request, @NotNull String resourcePath) {
+
+        try (ResourceResolver rr = getResourceResolver()) {
+            MappingChainContextInternal mappingContext = new MappingChainContextInternal(rr);
+            ResourceUri resourceUri = ResourceUriBuilder.forPath(resourcePath).build();
+            addIntermediateMapping(mappingContext, resourceUri, KEY_INTIAL);
+            if (resourceMappers != null) {
+                for (ResourceUriMapper mapper : resourceMappers) {
+                    String mapperName = mapper.getClass().getName();
+                    LOG.trace("map(): Using {} for {}", mapperName, resourceUri);
+                    ResourceUri input = resourceUri;
+                    resourceUri = mapper.map(resourceUri, request, mappingContext);
+                    if (input != resourceUri) {
+                        addIntermediateMapping(mappingContext, resourceUri, mapperName);
+                    }
+
+                    if (chancelChain(mappingContext, mapperName)) {
+                        break;
+                    }
+                }
+            }
+
+            if (LOG.isDebugEnabled()) {
+                logResult(mappingContext, "map()");
+            }
+
+            return new MappingChainResult(resourceUri, mappingContext.getContextHints(), mappingContext.getIntermediateMappings());
+        }
+    }
+
+    @NotNull
+    public MappingChainResult resolve(@Nullable HttpServletRequest request, @NotNull String path) {
+
+        try (ResourceResolver rr = getResourceResolver()) {
+            MappingChainContextInternal mappingContext = new MappingChainContextInternal(rr);
+
+            ResourceUri resourceUri = ResourceUriBuilder.parse(path, rr).build();
+            addIntermediateMapping(mappingContext, resourceUri, KEY_INTIAL);
+            if (resourceMappers != null) {
+                for (ResourceUriMapper mapper : reversedList(resourceMappers)) {
+                    String mapperName = mapper.getClass().getName();
+                    LOG.trace("resolve(): Using {} for {}", mapperName, resourceUri);
+                    ResourceUri input = resourceUri;
+                    resourceUri = mapper.resolve(resourceUri, request, mappingContext);
+                    if (input != resourceUri) {
+                        addIntermediateMapping(mappingContext, resourceUri, mapperName);
+                    }
+
+                    if (chancelChain(mappingContext, mapperName)) {
+                        break;
+                    }
+                }
+            }
+
+            if (LOG.isDebugEnabled()) {
+                logResult(mappingContext, "resolve()");
+            }
+
+            return new MappingChainResult(resourceUri, mappingContext.getContextHints(), mappingContext.getIntermediateMappings());
+        }
+
+    }
+
+
+    private boolean chancelChain(MappingChainContextInternal mappingContext, String name) {
+        if (mappingContext.isMarkedAsSkipRemainingChain()) {
+            LOG.debug("{} cancelled remaining chain", name);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private void addIntermediateMapping(MappingChainContextInternal mappingContext, ResourceUri resourceUri, String name) {
+        mappingContext.addIntermediateMapping(name, resourceUri);
+        LOG.trace("{} ajusted ResourceUri: {}", name, resourceUri);
+    }
+
+    private <T> List<T> reversedList(List<T> list) {
+        List<T> reversedList = new ArrayList<>(list);
+        Collections.reverse(reversedList);
+        return reversedList;
+    }
+
+    private void logResult(MappingChainContextInternal mappingContext, String method) {
+        Map<String, ResourceUri> intermediateMappings = mappingContext.getIntermediateMappings();
+        if (intermediateMappings.size() == 1) {
+            LOG.debug("\n{}:\nUNCHANGED {}", method, intermediateMappings.values().iterator().next());
+        } else {
+            Set<ContextHint> contextHints = mappingContext.getContextHints();
+            LOG.debug("\n{}:\n{}{}", method,
+                    intermediateMappings.entrySet().stream()
+                            .map(e -> StringUtils.leftPad(e.getKey().replaceAll("([a-z])[^.]*\\.(?=.*\\..*$)", "$1."), 50) + " -> "
+                                    + e.getValue())
+                            .collect(Collectors.joining("\n")),
+                    !contextHints.isEmpty()
+                            ? ("\nHints: " + contextHints.stream().map(ContextHint::getName).collect(Collectors.joining(",")))
+                            : "");
+        }
+    }
+
+    protected ResourceResolver getResourceResolver() {
+
+        try {
+            final Map<String, Object> authenticationInfo = new HashMap<>();
+            authenticationInfo.put(ResourceResolverFactory.SUBSERVICE, SUBSERVICE_NAME_MAPPING);
+            ResourceResolver serviceResourceResolver = resourceResolverFactory.getServiceResourceResolver(authenticationInfo);
+            return serviceResourceResolver;
+        } catch (LoginException e) {
+            throw new IllegalStateException("Cannot create ResourceResolver: " + e, e);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/defaultmappers/BasePathMapper.java b/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/defaultmappers/BasePathMapper.java
new file mode 100644
index 0000000..0530b33
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/defaultmappers/BasePathMapper.java
@@ -0,0 +1,83 @@
+/*
+ * 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.mappingchain.defaultmappers;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.uri.ResourceUri;
+import org.apache.sling.spi.resource.mapping.MappingChainContext;
+import org.apache.sling.spi.resource.mapping.ResourceUriMapper;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(configurationPolicy = ConfigurationPolicy.REQUIRE)
+@Designate(ocd = BasePathMapper.Config.class, factory = true)
+public class BasePathMapper implements ResourceUriMapper {
+    private static final Logger LOG = LoggerFactory.getLogger(BasePathMapper.class);
+
+    @ObjectClassDefinition(name = "Apache Sling Resource Mapper: Base Path", description = "Maps a base path in both ways")
+    public @interface Config {
+        @AttributeDefinition(name = "Content Base Path", description = "Content Base Path to be remove when mapping and to be added when resolving.")
+        String basePath();
+    }
+
+    private String basePath;
+
+    @Activate
+    public void activate(Config config) {
+        basePath = config.basePath();
+        LOG.info("Automatic addition of html extension active for paths {}", basePath);
+    }
+
+    @Override
+    public ResourceUri resolve(@NotNull ResourceUri resourceUri, HttpServletRequest request, MappingChainContext context) {
+
+        if (StringUtils.equals(resourceUri.getResourcePath(), "/") || StringUtils.equals(resourceUri.getResourcePath(), "")) {
+            return resourceUri;
+        }
+        String expandedPath = basePath + resourceUri.getResourcePath();
+        Resource resource = context.getResourceResolver().getResource(expandedPath);
+        if (resource != null) {
+            return resourceUri.adjust(b -> b.setResourcePath(expandedPath));
+        } else {
+            return resourceUri;
+        }
+    }
+
+    @Override
+    public ResourceUri map(@NotNull ResourceUri resourceUri, HttpServletRequest request, MappingChainContext context) {
+
+        if (StringUtils.startsWith(resourceUri.getResourcePath(), basePath)) {
+            return resourceUri.adjust(b -> b.setResourcePath(StringUtils.substringAfter(resourceUri.getResourcePath(), basePath)));
+        } else {
+            return resourceUri;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/defaultmappers/EnsureHtmlExtensionMapper.java b/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/defaultmappers/EnsureHtmlExtensionMapper.java
new file mode 100644
index 0000000..2a1ff9e
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mappingchain/defaultmappers/EnsureHtmlExtensionMapper.java
@@ -0,0 +1,80 @@
+/*
+ * 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.mappingchain.defaultmappers;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.resource.uri.ResourceUri;
+import org.apache.sling.spi.resource.mapping.MappingChainContext;
+import org.apache.sling.spi.resource.mapping.ResourceUriMapper;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(configurationPolicy = ConfigurationPolicy.REQUIRE)
+@Designate(ocd = EnsureHtmlExtensionMapper.Config.class)
+public class EnsureHtmlExtensionMapper implements ResourceUriMapper {
+    private static final Logger LOG = LoggerFactory.getLogger(EnsureHtmlExtensionMapper.class);
+
+    @ObjectClassDefinition(name = "Apache Sling Resource Mapper: Ensure HTML Extension", description = "Ensures html extension in links without extension")
+    public @interface Config {
+
+        @AttributeDefinition(name = "Include Regex", description = "Regex to restrict paths where the extension should automatically be added")
+        String includeRegex();
+    }
+
+    private String includeRegex;
+
+    @Activate
+    public void activate(Config config) {
+        includeRegex = config.includeRegex();
+        LOG.info("Automatic addition of html extension active for paths {}", StringUtils.defaultIfBlank(includeRegex, "<all>"));
+    }
+
+    private boolean matchesRegex(String path) {
+        return StringUtils.isBlank(includeRegex) || path.matches(includeRegex);
+    }
+
+    @Override
+    public ResourceUri resolve(@NotNull ResourceUri resourceUri, HttpServletRequest request, MappingChainContext context) {
+        return resourceUri;
+    }
+
+    @Override
+    public ResourceUri map(@NotNull ResourceUri resourceUri, HttpServletRequest request, MappingChainContext context) {
+        if (StringUtils.isNotBlank(resourceUri.getResourcePath())
+                && !StringUtils.contains(resourceUri.getResourcePath(), ".")
+                && StringUtils.isBlank(resourceUri.getExtension())
+                && matchesRegex(resourceUri.getResourcePath())) {
+            LOG.debug("Adding extension to URL {} with path={} and ext={}", resourceUri, resourceUri.getResourcePath(),
+                    resourceUri.getExtension());
+            return resourceUri.adjust(b -> b.setExtension("html"));
+        } else {
+            return resourceUri;
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
index ca2d6a6..380be0a 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
@@ -16,6 +16,31 @@
  */
 package org.apache.sling.resourceresolver.impl;
 
+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.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.createStringInterpolationProviderConfiguration;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.setInaccessibleField;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.setupStringInterpolationProvider;
+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;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
@@ -23,12 +48,14 @@ 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.StringInterpolationProvider;
+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.resourceresolver.util.MockTestUtil;
+import org.apache.sling.resourceresolver.util.MockTestUtil.ExpectedEtcMapping;
 import org.apache.sling.serviceusermapping.ServiceUserMapper;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
 import org.junit.Before;
@@ -39,31 +66,6 @@ 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.createStringInterpolationProviderConfiguration;
-import static org.apache.sling.resourceresolver.util.MockTestUtil.setInaccessibleField;
-import static org.apache.sling.resourceresolver.util.MockTestUtil.setupStringInterpolationProvider;
-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
@@ -130,6 +132,8 @@ public class EtcMappingResourceResolverTest {
         setInaccessibleField("observationPaths", activator, new Path[] {new Path("/")});
         ServiceUserMapper serviceUserMapper = mock(ServiceUserMapper.class);
         setInaccessibleField("serviceUserMapper", activator, serviceUserMapper);
+        setInaccessibleField("pathToUriMappingService", activator, MockTestUtil.createPathToUriMappingServiceMock(resourceResolver));
+
         commonFactory = spy(new CommonResourceResolverFactoryImpl(activator));
         when(bundleContext.getBundle()).thenReturn(bundle);
         when(bundleContext.getDataFile("vanityBloomFilter.txt")).thenReturn(vanityBloomFilterFile);
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 b960a82..b853473 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java
@@ -42,6 +42,7 @@ import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.resource.mapping.PathToUriMappingService;
 import org.apache.sling.api.wrappers.ValueMapDecorator;
 import org.apache.sling.resourceresolver.impl.mapping.MapEntries;
 import org.apache.sling.resourceresolver.impl.observation.ResourceChangeListenerWhiteboard;
@@ -49,6 +50,7 @@ import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderInfo;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderTracker;
+import org.apache.sling.resourceresolver.util.MockTestUtil;
 import org.apache.sling.serviceusermapping.ServiceUserMapper;
 import org.apache.sling.spi.resource.provider.QueryLanguageProvider;
 import org.apache.sling.spi.resource.provider.ResolveContext;
@@ -141,7 +143,12 @@ public class MockedResourceResolverImplTest {
     @SuppressWarnings("unchecked")
     @Before
     public void before() throws LoginException {
-        activator = new ResourceResolverFactoryActivator();
+        activator = new ResourceResolverFactoryActivator() {
+            @Override
+            public PathToUriMappingService getPathToUriMappingService() {
+                return MockTestUtil.createPathToUriMappingServiceMock(null);
+            }
+        };
 
         // system bundle access
         final Bundle systemBundle = Mockito.mock(Bundle.class);
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/ProviderHandlerTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/ProviderHandlerTest.java
index d9592c4..20b3d17 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/ProviderHandlerTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/ProviderHandlerTest.java
@@ -34,6 +34,7 @@ import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorageProvider;
+import org.apache.sling.resourceresolver.util.MockTestUtil;
 import org.apache.sling.spi.resource.provider.ResolveContext;
 import org.apache.sling.spi.resource.provider.ResourceContext;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
@@ -67,6 +68,7 @@ public class ProviderHandlerTest {
                     return new ResourceProviderStorage(Arrays.asList(h));
                 }
             });
+        activator.pathToUriMappingService = MockTestUtil.createPathToUriMappingServiceMock(resolver);
 
         final Resource parent = resolver.getResource(ResourceUtil.getParent(servletpath));
         assertNotNull("Parent must be available", parent);
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/ResourceDecoratorTestBase.java b/src/test/java/org/apache/sling/resourceresolver/impl/ResourceDecoratorTestBase.java
index 8213179..6795875 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/ResourceDecoratorTestBase.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/ResourceDecoratorTestBase.java
@@ -36,10 +36,12 @@ 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.mapping.PathToUriMappingService;
 import org.apache.sling.resourceresolver.impl.helper.ResourceDecoratorTracker;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorageProvider;
+import org.apache.sling.resourceresolver.util.MockTestUtil;
 import org.apache.sling.spi.resource.provider.QueryLanguageProvider;
 import org.apache.sling.spi.resource.provider.ResolveContext;
 import org.apache.sling.spi.resource.provider.ResourceContext;
@@ -141,6 +143,11 @@ public abstract class ResourceDecoratorTestBase {
             public ResourceAccessSecurityTracker getResourceAccessSecurityTracker() {
                 return new ResourceAccessSecurityTracker();
             }
+
+            @Override
+            public PathToUriMappingService getPathToUriMappingService() {
+                return MockTestUtil.createPathToUriMappingServiceMock(null);
+            }
         };
 
         final List<ResourceProviderHandler> list = Arrays.asList(MockedResourceResolverImplTest.createRPHandler(provider, "A-provider", 0L, "/"));
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverImplTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverImplTest.java
index 3eee2cf..fde657b 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverImplTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverImplTest.java
@@ -52,6 +52,7 @@ import org.apache.sling.api.resource.SyntheticResource;
 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.resourceresolver.util.MockTestUtil;
 import org.apache.sling.spi.resource.provider.ResolveContext;
 import org.apache.sling.spi.resource.provider.ResourceContext;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
@@ -91,6 +92,7 @@ public class ResourceResolverImplTest {
         ResourceResolverFactoryActivator activator = new ResourceResolverFactoryActivator();
         activator.resourceProviderTracker = resourceProviderTracker;
         activator.resourceAccessSecurityTracker = new ResourceAccessSecurityTracker();
+        activator.pathToUriMappingService = MockTestUtil.createPathToUriMappingServiceMock(null);
         commonFactory = new CommonResourceResolverFactoryImpl(activator);
         final Bundle usingBundle = mock(Bundle.class);
         resFac = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null);
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverMangleNamespacesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverMangleNamespacesTest.java
index 8877882..dabc021 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverMangleNamespacesTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverMangleNamespacesTest.java
@@ -23,19 +23,21 @@ import static org.junit.Assert.assertEquals;
 import java.util.Arrays;
 import java.util.Iterator;
 
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.annotations.NotNull;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
 import org.apache.commons.collections4.IteratorUtils;
 import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.mapping.PathToUriMappingService;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorageProvider;
+import org.apache.sling.resourceresolver.util.MockTestUtil;
 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.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -65,6 +67,11 @@ public class ResourceResolverMangleNamespacesTest {
             public boolean isMangleNamespacePrefixes() {
                 return true;
             }
+
+            @Override
+            public PathToUriMappingService getPathToUriMappingService() {
+                return MockTestUtil.createPathToUriMappingServiceMock(rr);
+            }
         };
 
         Mockito.when(mockedSession.getNamespacePrefix(NS_PREFIX)).thenReturn(NS_URL);
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 44e04a9..cf54a3b 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,6 +16,31 @@
  */
 package org.apache.sling.resourceresolver.impl.mapping;
 
+import static org.apache.sling.resourceresolver.util.MockTestUtil.createStringInterpolationProviderConfiguration;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.setupStringInterpolationProvider;
+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;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+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 org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
@@ -23,6 +48,7 @@ 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.apache.sling.resourceresolver.impl.ResourceResolverFactoryImpl;
 import org.junit.After;
 import org.junit.Before;
 import org.mockito.Mock;
@@ -33,31 +59,6 @@ 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.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.util.MockTestUtil.createStringInterpolationProviderConfiguration;
-import static org.apache.sling.resourceresolver.util.MockTestUtil.setupStringInterpolationProvider;
-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
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 a98ca6e..1f3fa13 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
@@ -16,6 +16,23 @@
  */
 package org.apache.sling.resourceresolver.impl.mapping;
 
+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.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.path.Path;
@@ -26,6 +43,8 @@ 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.resourceresolver.util.MockTestUtil;
+import org.apache.sling.resourceresolver.util.MockTestUtil.ExpectedEtcMapping;
 import org.apache.sling.serviceusermapping.ServiceUserMapper;
 import org.apache.sling.spi.resource.provider.ResolveContext;
 import org.apache.sling.spi.resource.provider.ResourceContext;
@@ -34,23 +53,6 @@ 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 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.anyString;
-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.
@@ -191,7 +193,7 @@ public class EtcMappingMapEntriesTest extends AbstractMappingMapEntriesTest {
         final Bundle usingBundle = mock(Bundle.class);
         ResourceResolverFactoryImpl resFac = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null);
         ResourceResolver resResolver = resFac.getAdministrativeResourceResolver(null);
-
+        when(activator.getPathToUriMappingService()).thenReturn(MockTestUtil.createPathToUriMappingServiceMock(resResolver));
         HttpServletRequest request = mock(HttpServletRequest.class);
         when(request.getScheme()).thenReturn("http");
         when(request.getServerName()).thenReturn("localhost");
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImplTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImplTest.java
index 0e2cc54..fa71d15 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImplTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImplTest.java
@@ -39,9 +39,11 @@ 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.mapping.PathToUriMappingService;
 import org.apache.sling.api.resource.mapping.ResourceMapper;
 import org.apache.sling.resourceresolver.impl.ResourceAccessSecurityTracker;
 import org.apache.sling.resourceresolver.impl.ResourceResolverFactoryActivator;
+import org.apache.sling.resourceresolver.util.MockTestUtil;
 import org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
 import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
@@ -97,6 +99,7 @@ public class ResourceMapperImplTest {
         ctx.registerInjectActivateService(new ServiceUserMapperImpl());
         ctx.registerInjectActivateService(new ResourceAccessSecurityTracker());
         ctx.registerInjectActivateService(new StringInterpolationProviderImpl());
+        ctx.registerService(PathToUriMappingService.class, MockTestUtil.createPathToUriMappingServiceMock(resolver));
 
         InMemoryResourceProvider resourceProvider = new InMemoryResourceProvider();
         resourceProvider.putResource("/"); // root
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 5291b5b..5009b91 100644
--- a/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
+++ b/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
@@ -16,12 +16,35 @@
  */
 package org.apache.sling.resourceresolver.util;
 
-import junit.framework.TestCase;
+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;
+
+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.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
 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.resource.mapping.PathToUriMappingService;
+import org.apache.sling.api.resource.mapping.PathToUriMappingService.ContextHint;
+import org.apache.sling.api.resource.uri.ResourceUri;
 import org.apache.sling.api.wrappers.ValueMapDecorator;
 import org.apache.sling.resourceresolver.impl.SimpleValueMapImpl;
 import org.apache.sling.resourceresolver.impl.helper.RedirectResource;
@@ -31,27 +54,13 @@ import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvide
 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.jetbrains.annotations.NotNull;
 import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.osgi.framework.BundleContext;
 
-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;
+import junit.framework.TestCase;
 
 public class MockTestUtil {
 
@@ -424,4 +433,9 @@ public class MockTestUtil {
             assertEquals("Wrong Redirect Type (ext/int) for: " + title, this.internal, mapEntry.isInternal());
         }
     }
+
+    public static PathToUriMappingService createPathToUriMappingServiceMock(final ResourceResolver rr) {
+        return new TestPathToUriMappingService();
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/resourceresolver/util/TestPathToUriMappingService.java b/src/test/java/org/apache/sling/resourceresolver/util/TestPathToUriMappingService.java
new file mode 100644
index 0000000..a586e67
--- /dev/null
+++ b/src/test/java/org/apache/sling/resourceresolver/util/TestPathToUriMappingService.java
@@ -0,0 +1,65 @@
+/*
+ * 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 java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.resource.mapping.PathToUriMappingService;
+import org.apache.sling.api.resource.uri.ResourceUri;
+import org.apache.sling.api.resource.uri.ResourceUriBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+class TestPathToUriMappingService implements PathToUriMappingService {
+
+    static class MappingResult implements PathToUriMappingService.Result {
+        private final @NotNull ResourceUri resourceUri;
+
+        MappingResult(@NotNull ResourceUri resourceUri) {
+            this.resourceUri = resourceUri;
+        }
+
+        @Override
+        public @NotNull ResourceUri getResourceUri() {
+            return resourceUri;
+        }
+
+        @Override
+        public @NotNull Set<ContextHint> getContextHints() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public @NotNull Map<String, ResourceUri> getIntermediateMappings() {
+            return Collections.emptyMap();
+        }
+    }
+
+    @Override
+    public Result resolve(@Nullable HttpServletRequest request, @NotNull String path) {
+        return new MappingResult(ResourceUriBuilder.parse(path, null).build());
+    }
+
+    @Override
+    public Result map(@Nullable HttpServletRequest request, @NotNull String resourcePath) {
+        return new MappingResult(ResourceUriBuilder.forPath(resourcePath).build());
+    }
+}
\ No newline at end of file