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 2019/07/30 16:17:09 UTC

[sling-org-apache-sling-resourceresolver] branch feature/SLING-7768 updated (f4aae98 -> bdc07c8)

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 f4aae98  SLING-7768 Added the String Interpolation tests to the EtcMappingResourceResolverTest to make sure the Sling Interpolation works with Resource Resolving
     add 5ef614e  Use released API 2.18.2
     add 2e85a39  [maven-release-plugin] prepare release org.apache.sling.resourceresolver-1.6.2
     add 6f86f7d  [maven-release-plugin] prepare for next development iteration
     add c11a78e  Revert "SLING-7544 - Make optimized alias lookup non-blocking"
     add d36a97d  [maven-release-plugin] prepare release org.apache.sling.resourceresolver-1.6.4
     add 2ceaa44  [maven-release-plugin] prepare for next development iteration
     add 0512d26  SLING-7798 : Switch from JSR-305 annotations to Jetbrains Nullable/NotNull Annotations
     add 6d5f23f  Added Tests for the Etc Mapping through Map Entries and Resource Resolver
     add 9271b58  Added an Etc Mapping Test to showcase the endless circular mapping
     add 384172e  Merge branch 'master' of github.com:apache/sling-org-apache-sling-resourceresolver
     add 85c1913  SLING-7665 Use bnd Maven plugins
     add 3efac62  remove some unused imports
     add 8ee602c  remove versions already defined in parent pom
     add 78bb6e3  update to osgi-mock 2.3.10 restructure tests to avoid registering multiple version of ResourceProviderTracker (no change in test logic)
     add 2e21a74  get service reference from service registration - getting the first one from all service references for this path may return the wrong one
     add b9e7429  do not register the same resource provider mock twice
     add f2e87e4  fix unit test to desired behavior: register overlay resource provider with higher service ranking with osgi-mock 1.4.0 it worked before because this hat a flaw in comparing service references
     add 8a88532  Merge branch 'feature/update-testing-deps'
     add b01bea8  SLING-7792 - Resource Resolver should return more than one resolved path if available
     add bdfa9e3  SLING-7792 - Resource Resolver should return more than one resolved path if available
     add def05b6  SLING-7792 - Resource Resolver should return more than one resolved path if available
     add 9ff4836  SLING-7792 - Resource Resolver should return more than one resolved path if available
     add 69c3a3e  SLING-7792 - Resource Resolver should return more than one resolved path if available
     add 59a452a  Use released version of the maven-sling-plugin
     add 3ad6de6  Use latest API bundle
     add 296c5f3  [maven-release-plugin] prepare release org.apache.sling.resourceresolver-1.6.6
     add 38142f3  [maven-release-plugin] prepare for next development iteration
     add 34f301b  SLING-7881 Resource resolver may calculate incorrect map path for an address that is a selector on the root resource
     add 411817f  SLING-7216 - [nice-to-have] Add a CODE_OF_CONDUCT file to every module
     add 72855c2  SLING-7215 - [nice-to-have] Add a CONTRIBUTING file to every module
     add e1587c2  trivial: added license header to *.md files
     add bbccfed  SLING-8006: Remove empty list of handlers if we removed the last handler from a handler list for a given path because the handler didn't activate.
     add cbf85dd  SLING-8006: Double check that the list is not used when empty to make sure.
     add fd5afba  [maven-release-plugin] prepare release org.apache.sling.resourceresolver-1.6.8
     add e017ace  [maven-release-plugin] prepare for next development iteration
     add 92935ad  SLING-7245 - Validate pull requests using Jenkins
     add 4593aa9  Updating badges for org-apache-sling-resourceresolver
     add 38f975e  SLING-8341 : find extra slashes and ignore redundant delimiteres
     add fedf548  SLING-8341 : Added test case for extra slash delim in path
     add 0ba947f  SLING-8335 :StringIndexOutOfBoundsException from resolver.map with non absolute path check for absolute path at the beginning of the method
     add 03f2f1a  SLING-8335 - test case to assert a relative path mapping returns the original path itself
     add f5e8226  Merge pull request #12 from ashokpanghal/issues/SLING-8335
     add 6b7a8c8  Merge branch 'master' into issues/SLING-8341
     add 25c97da  Merge pull request #13 from ashokpanghal/issues/SLING-8341
     add 7b8fefc  [maven-release-plugin] prepare release org.apache.sling.resourceresolver-1.6.10
     add 87eb575  [maven-release-plugin] prepare for next development iteration
     add 96bbbbb  Merge pull request #1 from apache/master
     add 54721c2  SLING-8349 : resolver.map - map empty path to root path
     add 3ad934d  Merge pull request #14 from ashokpanghal/issues/SLING-8349
     add 3df077a  [maven-release-plugin] prepare release org.apache.sling.resourceresolver-1.6.12
     add 5dcaacc  [maven-release-plugin] prepare for next development iteration
     new 903cbd8  Merge branch 'master' into feature/SLING-7768
     new bdc07c8  Migrated the Sling Interplation to Lang3 StrSubtitutor, adjusted the tests and fixed some merge problems

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:
 CODE_OF_CONDUCT.md                                 |  22 ++
 CONTRIBUTING.md                                    |  24 ++
 .../providers/tree/Pathable.java => Jenkinsfile    |   9 +-
 README.md                                          |  58 +++-
 bnd.bnd                                            |  17 +
 pom.xml                                            |  27 +-
 .../impl/CommonResourceResolverFactoryImpl.java    |  20 +-
 .../impl/ResourceResolverFactoryActivator.java     |   5 -
 .../impl/ResourceResolverFactoryConfig.java        |   6 -
 .../impl/ResourceResolverImpl.java                 | 236 ++-----------
 .../resourceresolver/impl/ResourceTypeUtil.java    |   8 +-
 .../console/ResourceResolverWebConsolePlugin.java  |  34 +-
 .../impl/helper/ResourceResolverContext.java       |   4 +-
 .../impl/helper/ResourceResolverControl.java       |  20 +-
 .../impl/legacy/LegacyResourceProviderAdapter.java |  28 +-
 .../LegacyResourceProviderFactoryAdapter.java      |  36 +-
 .../impl/mapping/MapConfigurationProvider.java     |   4 -
 .../resourceresolver/impl/mapping/MapEntries.java  |  92 +----
 .../impl/mapping/MapEntriesHandler.java            |   7 -
 .../resourceresolver/impl/mapping/MapEntry.java    |  20 +-
 .../impl/mapping/ResourceMapperImpl.java           | 382 +++++++++++++++++++++
 .../impl/mapping/StringInterpolationProvider.java  |  68 +---
 .../StringInterpolationProviderConfiguration.java  |  24 ++
 .../mapping/StringInterpolationProviderImpl.java   | 114 ++----
 .../impl/providers/ResourceProviderTracker.java    |  13 +-
 .../stateful/AuthenticatedResourceProvider.java    |  14 +-
 .../providers/stateful/BasicResolveContext.java    |  14 +-
 .../impl/providers/stateful/ProviderManager.java   |  28 +-
 .../impl/EtcMappingResourceResolverTest.java       |  52 ++-
 .../sling/resourceresolver/impl/Fixture.java       |  17 +-
 .../impl/MockedResourceResolverImplTest.java       |  16 +-
 .../impl/ResourceResolverMangleNamespacesTest.java |   8 +-
 .../resourceresolver/impl/SimpleValueMapImpl.java  |   7 +-
 .../mapping/AbstractMappingMapEntriesTest.java     |  10 +-
 .../impl/mapping/InMemoryResource.java             |  96 ++++++
 .../impl/mapping/InMemoryResourceProvider.java     |  79 +++++
 .../impl/mapping/MapEntriesTest.java               | 286 +++++----------
 .../impl/mapping/MapEntryTest.java                 |  60 ++++
 .../impl/mapping/ResourceMapperImplTest.java       | 285 +++++++++++++++
 .../mapping/StringInterpolationMapEntriesTest.java |   4 +-
 .../StringInterpolationProviderImplTest.java       | 148 +++++---
 .../providers/ResourceProviderTrackerTest.java     |  51 ++-
 .../sling/resourceresolver/util/MockTestUtil.java  | 168 +++++++--
 43 files changed, 1709 insertions(+), 912 deletions(-)
 create mode 100644 CODE_OF_CONDUCT.md
 create mode 100644 CONTRIBUTING.md
 copy src/main/java/org/apache/sling/resourceresolver/impl/providers/tree/Pathable.java => Jenkinsfile (87%)
 create mode 100644 bnd.bnd
 create mode 100644 src/main/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImpl.java
 create mode 100644 src/test/java/org/apache/sling/resourceresolver/impl/mapping/InMemoryResource.java
 create mode 100644 src/test/java/org/apache/sling/resourceresolver/impl/mapping/InMemoryResourceProvider.java
 create mode 100644 src/test/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImplTest.java


[sling-org-apache-sling-resourceresolver] 01/02: Merge branch 'master' into feature/SLING-7768

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 903cbd84840ff76f899f02f763dc502412a846c7
Merge: f4aae98 5dcaacc
Author: Andreas Schaefer <sc...@iMac.local>
AuthorDate: Mon Jul 29 08:48:15 2019 -0700

    Merge branch 'master' into feature/SLING-7768
    
    # Conflicts:
    #	pom.xml
    #	src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
    #	src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
    #	src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java
    #	src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
    #	src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java
    #	src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java
    #	src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java

 CODE_OF_CONDUCT.md                                 |  22 ++
 CONTRIBUTING.md                                    |  24 ++
 Jenkinsfile                                        |  20 ++
 README.md                                          |   4 +-
 bnd.bnd                                            |  17 +
 pom.xml                                            |  27 +-
 .../impl/CommonResourceResolverFactoryImpl.java    |  20 +-
 .../impl/ResourceResolverFactoryActivator.java     |   5 -
 .../impl/ResourceResolverFactoryConfig.java        |   6 -
 .../impl/ResourceResolverImpl.java                 | 236 ++-----------
 .../resourceresolver/impl/ResourceTypeUtil.java    |   8 +-
 .../console/ResourceResolverWebConsolePlugin.java  |  34 +-
 .../impl/helper/ResourceResolverContext.java       |   4 +-
 .../impl/helper/ResourceResolverControl.java       |  20 +-
 .../impl/legacy/LegacyResourceProviderAdapter.java |  28 +-
 .../LegacyResourceProviderFactoryAdapter.java      |  36 +-
 .../impl/mapping/MapConfigurationProvider.java     |   4 -
 .../resourceresolver/impl/mapping/MapEntries.java  |  38 +-
 .../impl/mapping/MapEntriesHandler.java            |   7 -
 .../resourceresolver/impl/mapping/MapEntry.java    |  20 +-
 .../impl/mapping/ResourceMapperImpl.java           | 382 +++++++++++++++++++++
 .../impl/providers/ResourceProviderTracker.java    |  13 +-
 .../stateful/AuthenticatedResourceProvider.java    |  14 +-
 .../providers/stateful/BasicResolveContext.java    |  14 +-
 .../impl/providers/stateful/ProviderManager.java   |  28 +-
 .../impl/EtcMappingResourceResolverTest.java       |  63 ++++
 .../sling/resourceresolver/impl/Fixture.java       |  17 +-
 .../impl/MockedResourceResolverImplTest.java       |  16 +-
 .../impl/ResourceResolverMangleNamespacesTest.java |   8 +-
 .../resourceresolver/impl/SimpleValueMapImpl.java  |   7 +-
 .../mapping/AbstractMappingMapEntriesTest.java     |  21 ++
 .../impl/mapping/EtcMappingMapEntriesTest.java     |  13 +
 .../impl/mapping/InMemoryResource.java             |  96 ++++++
 .../impl/mapping/InMemoryResourceProvider.java     |  79 +++++
 .../impl/mapping/MapEntriesTest.java               | 292 +++++-----------
 .../impl/mapping/MapEntryTest.java                 |  60 ++++
 .../impl/mapping/ResourceMapperImplTest.java       | 284 +++++++++++++++
 .../providers/ResourceProviderTrackerTest.java     |  51 ++-
 .../sling/resourceresolver/util/MockTestUtil.java  | 155 +++++++++
 39 files changed, 1571 insertions(+), 622 deletions(-)

diff --cc pom.xml
index 5466e8a,0b35127..cea7528
--- a/pom.xml
+++ b/pom.xml
@@@ -63,29 -62,14 +62,40 @@@
                      </execution>
                  </executions>
              </plugin>
++<!--
 +            <plugin>
 +                <groupId>org.apache.felix</groupId>
 +                <artifactId>maven-bundle-plugin</artifactId>
 +                <extensions>true</extensions>
 +                <configuration>
 +                    <instructions>
 +                        <Import-Package>
 +                            javax.jcr;resolution:=optional,
 +                            *
 +                        </Import-Package>
++                        <!- - Check if that is still needed - ->
 +                        <Export-Package>
 +                            org.apache.sling.resourceresolver.impl.mapping
 +                        </Export-Package>
 +                        <Provide-Capability>
 +                            osgi.service;objectClass=javax.servlet.Servlet,
 +                            osgi.service;objectClass=org.apache.sling.api.resource.ResourceResolverFactory,
 +                            osgi.service;objectClass=org.apache.sling.api.resource.observation.ResourceChangeListener,
 +                            osgi.service;objectClass=org.apache.sling.api.resource.runtime.RuntimeService,
 +                            osgi.service;objectClass=org.apache.sling.spi.resource.provider.ResourceProvider
 +                        </Provide-Capability>
 +                    </instructions>
 +                </configuration>
 +            </plugin>
++-->
+             <plugin>
+                 <groupId>biz.aQute.bnd</groupId>
+                 <artifactId>bnd-maven-plugin</artifactId>
+             </plugin>
+             <plugin>
+                 <groupId>biz.aQute.bnd</groupId>
+                 <artifactId>bnd-baseline-maven-plugin</artifactId>
+             </plugin>
              <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-javadoc-plugin</artifactId>
diff --cc src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
index 478be4a,ffbcb2d..35196a2
--- a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
@@@ -37,9 -37,7 +37,8 @@@ import org.apache.sling.api.resource.Re
  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;
  import org.apache.sling.resourceresolver.impl.providers.ResourceProviderTracker;
  import org.apache.sling.resourceresolver.impl.providers.ResourceProviderTracker.ChangeListener;
diff --cc src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
index 89fa39b,9779b3b..2efa826
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
@@@ -53,8 -52,7 +53,9 @@@ import java.util.concurrent.locks.Reent
  import javax.servlet.http.HttpServletResponse;
  
  import org.apache.sling.api.SlingConstants;
 +import org.apache.sling.api.SlingException;
  import org.apache.sling.api.resource.LoginException;
++import org.apache.sling.api.resource.QuerySyntaxException;
  import org.apache.sling.api.resource.Resource;
  import org.apache.sling.api.resource.ResourceResolver;
  import org.apache.sling.api.resource.ResourceUtil;
@@@ -164,8 -151,7 +163,7 @@@ public class MapEntries implement
          this.resolveMapsMap = Collections.singletonMap(GLOBAL_LIST_KEY, (List<MapEntry>)Collections.EMPTY_LIST);
          this.mapMaps = Collections.<MapEntry> emptyList();
          this.vanityTargets = Collections.<String,List <String>>emptyMap();
 -        this.aliasMap = Collections.<String, Map<String, String>>emptyMap();
 +        this.aliasMap = Collections.emptyMap();
-         this.stringInterpolationProvider = stringInterpolationProvider;
  
          doInit();
  
diff --cc src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
index 0fb93fc,b8f73e1..9ae3f7d
--- a/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
@@@ -23,8 -23,6 +23,11 @@@ import org.apache.sling.api.resource.ob
  import org.apache.sling.api.resource.path.Path;
  import org.apache.sling.resourceresolver.impl.mapping.MapConfigurationProvider;
  import org.apache.sling.resourceresolver.impl.mapping.MapEntries;
++<<<<<<< HEAD
 +import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderConfiguration;
 +import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderImpl;
++=======
++>>>>>>> master
  import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler;
  import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
  import org.apache.sling.resourceresolver.impl.providers.ResourceProviderTracker;
@@@ -55,7 -53,6 +58,10 @@@ import static org.apache.sling.resource
  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;
++<<<<<<< HEAD
 +import static org.apache.sling.resourceresolver.util.MockTestUtil.setupStringInterpolationProvider;
++=======
++>>>>>>> master
  import static org.mockito.Matchers.any;
  import static org.mockito.Matchers.anyString;
  import static org.mockito.Mockito.mock;
@@@ -90,10 -87,6 +96,13 @@@ public class EtcMappingResourceResolver
      @Mock
      ResourceProvider<?> resourceProvider;
  
++<<<<<<< HEAD
 +    @Mock
 +    StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration;
 +
 +    StringInterpolationProviderImpl stringInterpolationProvider = new StringInterpolationProviderImpl();
++=======
++>>>>>>> master
      MapEntries mapEntries;
  
      File vanityBloomFilterFile;
@@@ -122,7 -115,6 +131,10 @@@
          setInaccessibleField("resourceProviderTracker", activator, resourceProviderTracker);
          setInaccessibleField("resourceAccessSecurityTracker", activator, new ResourceAccessSecurityTracker());
          setInaccessibleField("bundleContext", activator, bundleContext);
++<<<<<<< HEAD
 +        setInaccessibleField("stringInterpolationProvider", activator, stringInterpolationProvider);
++=======
++>>>>>>> master
          setInaccessibleField("mapRoot", activator, "/etc/map");
          setInaccessibleField("mapRootPrefix", activator, "/etc/map");
          setInaccessibleField("observationPaths", activator, new Path[] {new Path("/")});
@@@ -133,7 -125,7 +145,11 @@@
          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
++<<<<<<< HEAD
 +        callInaccessibleMethod("activate", commonFactory, BundleContext.class, bundleContext);
++=======
+         callInaccessibleMethod("activate", null, commonFactory, BundleContext.class, bundleContext);
++>>>>>>> master
          final Bundle usingBundle = mock(Bundle.class);
          resourceResolverFactory = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null);
          resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
@@@ -147,6 -139,15 +163,18 @@@
          return new ArrayList<>();
      }
  
++<<<<<<< HEAD
++=======
+     /**
+      * Changes to the /etc/map in our tests are not taking effect until there is an Change Event issued
+      *
+      * ATTENTION: this method can only be issued once. After that the Resource Metadata is locked and
+      * hence updates will fail
+      *
+      * @param path Path to the resource root to be refreshed
+      * @param isExternal External flag of the ResourceChange event
+      */
++>>>>>>> master
      void refreshMapEntries(String path, boolean isExternal) {
          ((MapEntries) commonFactory.getMapEntries()).onChange(
              asList(
@@@ -272,39 -273,37 +300,74 @@@
          checkInternalResource(resolvedResource, "/anecdotes/stories");
      }
  
++<<<<<<< HEAD
 +    @Test
 +    public void simple_node_string_interpolation() throws Exception {
 +        buildResource("${siv.one}", http, resourceResolver, resourceProvider,PROP_REDIRECT_EXTERNAL, "/content/simple-node");
 +        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, bundleContext, new String[] {"siv.one=test-simple-node.80"});
 +
 +        refreshMapEntries("/etc/map", true);
 +
 +        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-node.80/", "/content/simple-node/");
 +        expectedEtcMapping.assertEtcMap("String Interpolation for simple match", commonFactory.getMapEntries().getResolveMaps());
 +
 +        Resource content = buildResource("/content", null, resourceResolver, resourceProvider);
 +        Resource simpleNode = buildResource("/content/simple-node", content, resourceResolver, resourceProvider);
 +
 +        HttpServletRequest request = createRequestFromUrl("http://test-simple-node:80/");
 +        Resource resolvedResource = resourceResolver.resolve(request, "/");
 +        checkRedirectResource(resolvedResource, "/content/simple-node/", 302);
 +    }
 +
 +    @Test
 +    public void simple_match_string_interpolation() throws Exception {
 +        buildResource("test-node", http, resourceResolver, resourceProvider,
 +            PROP_REG_EXP, "${siv.one}/",
 +            PROP_REDIRECT_EXTERNAL, "/content/simple-match/"
 +        );
 +        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, bundleContext, new String[] {"siv.one=test-simple-match.80"});
 +
 +        refreshMapEntries("/etc/map", true);
 +
 +        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-match.80/", "/content/simple-match/");
 +        expectedEtcMapping.assertEtcMap("String Interpolation for simple match", commonFactory.getMapEntries().getResolveMaps());
 +
 +        HttpServletRequest request = createRequestFromUrl("http://test-simple-match:80/");
 +        Resource resolvedResource = resourceResolver.resolve(request, "/");
 +        checkRedirectResource(resolvedResource, "/content/simple-match/", 302);
++=======
+     /**
+      * ATTENTION: this tests showcases an erroneous condition of an endless circular mapping in the /etc/map. When
+      * this test passes this condition is present. After a fix this test must be adjusted.
+      * 
+      * This confirms an issue with the Etc Mapping where a mapping from a node to a child node (here / to /content)
+      * ends up in a endless circular mapping.
+      * The only way to recover from this is to go to the OSGi console and change the /etc/map path in the Resource
+      * Resolver factory.
+      * Either the Etc Mapping discovers this condition and stops it or at least ignores mapping for Composum to allow
+      * the /etc/map to be edited.
+      */
+     @Test
+     public void endless_circular_mapping() throws Exception {
+         buildResource(http.getPath() + "/localhost.8080", http, resourceResolver, resourceProvider, PROP_REDIRECT_EXTERNAL, "/content");
+         refreshMapEntries("/etc/map", true);
+ 
+         ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/");
+         expectedEtcMapping.assertEtcMap("Etc Mapping for root node to content", commonFactory.getMapEntries().getResolveMaps());
+ 
+         buildResource("/content/test", null, resourceResolver, resourceProvider);
+         buildResource("/content/content/test", null, resourceResolver, resourceProvider);
+         buildResource("/content/content/content/test", null, resourceResolver, resourceProvider);
+ 
+         HttpServletRequest request = createRequestFromUrl("http://localhost:8080/");
+         Resource resolvedResource = resourceResolver.resolve(request, "/test.html");
+         checkRedirectResource(resolvedResource, "/content/test.html", 302);
+ 
+         resolvedResource = resourceResolver.resolve(request, "/content/test.html");
+         checkRedirectResource(resolvedResource, "/content/content/test.html", 302);
+ 
+         resolvedResource = resourceResolver.resolve(request, "/content/content/test.html");
+         checkRedirectResource(resolvedResource, "/content/content/content/test.html", 302);
++>>>>>>> master
      }
  }
diff --cc src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
index 3e1ac90,6a5f9e2..e7129da
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
@@@ -46,7 -46,6 +46,10 @@@ import java.util.concurrent.ExecutorSer
  import java.util.concurrent.Future;
  import java.util.concurrent.Semaphore;
  
++<<<<<<< HEAD
 +import static org.apache.sling.resourceresolver.util.MockTestUtil.setupStringInterpolationProvider;
++=======
++>>>>>>> master
  import static org.junit.Assert.fail;
  import static org.mockito.Matchers.any;
  import static org.mockito.Matchers.anyMap;
@@@ -79,10 -78,6 +82,13 @@@ public abstract class AbstractMappingMa
      @Mock
      ResourceResolver resourceResolver;
  
++<<<<<<< HEAD
 +    @Mock
 +    StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration;
 +
 +    StringInterpolationProviderImpl stringInterpolationProvider = new StringInterpolationProviderImpl();
++=======
++>>>>>>> master
      MapEntries mapEntries;
  
      File vanityBloomFilterFile;
@@@ -106,7 -101,6 +112,10 @@@
          when(resourceResolverFactory.isVanityPathEnabled()).thenReturn(true);
          when(resourceResolverFactory.getVanityPathConfig()).thenReturn(configs);
          when(resourceResolverFactory.isOptimizeAliasResolutionEnabled()).thenReturn(true);
++<<<<<<< HEAD
 +        when(resourceResolverFactory.isForceNoAliasTraversal()).thenReturn(true);
++=======
++>>>>>>> master
          when(resourceResolverFactory.getObservationPaths()).thenReturn(new Path[] {new Path("/")});
          when(resourceResolverFactory.getMapRoot()).thenReturn(MapEntries.DEFAULT_MAP_ROOT);
          when(resourceResolverFactory.getMaxCachedVanityPathEntries()).thenReturn(-1L);
@@@ -117,8 -111,7 +126,12 @@@
          map = setupEtcMapResource("/etc", "map");
          http = setupEtcMapResource("http", map);
  
++<<<<<<< HEAD
 +        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, bundleContext, new String[] {});
 +        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
++=======
+         mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin);
++>>>>>>> master
  
          final Field aliasMapField = MapEntries.class.getDeclaredField("aliasMap");
          aliasMapField.setAccessible(true);
@@@ -191,7 -184,7 +204,11 @@@
          return resource;
      }
  
++<<<<<<< HEAD
 +    MapEntriesTest.DataFuture createDataFuture(ExecutorService pool, final MapEntries mapEntries) {
++=======
+     DataFuture createDataFuture(ExecutorService pool, final MapEntries mapEntries) {
++>>>>>>> master
  
          Future<Iterator<?>> future = pool.submit(new Callable<Iterator<?>>() {
              @Override
@@@ -199,7 -192,7 +216,11 @@@
                  return mapEntries.getResolveMapsIterator("http/localhost.8080/target/justVanityPath");
              }
          });
++<<<<<<< HEAD
 +        return new MapEntriesTest.DataFuture(future);
++=======
+         return new DataFuture(future);
++>>>>>>> master
      }
  
      void simulateSomewhatSlowSessionOperation(final Semaphore sessionLock) throws InterruptedException {
diff --cc src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java
index a98ca6e,a7d09ca..bfede24
--- 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,40 -16,12 +16,49 @@@
   */
  package org.apache.sling.resourceresolver.impl.mapping;
  
++<<<<<<< HEAD
 +import org.apache.sling.api.resource.Resource;
 +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 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;
++=======
+ import static org.apache.sling.resourceresolver.impl.ResourceResolverImpl.PROP_REDIRECT_INTERNAL;
+ import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
+ 
+ import org.apache.sling.api.resource.Resource;
+ import org.apache.sling.resourceresolver.util.MockTestUtil.ExpectedEtcMapping;
+ import org.junit.Test;
++>>>>>>> master
  
  /**
   * These tests are for the /etc/map setup of the Map Entries when
@@@ -127,125 -99,5 +136,129 @@@ public class EtcMappingMapEntriesTest e
              .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());
++<<<<<<< HEAD
 +
 +        // 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();
 +//    }
++=======
+     }
++>>>>>>> master
  }
diff --cc src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java
index b61ac20,9b11f48..db072c3
--- 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,19 -16,18 +16,20 @@@
   */
  package org.apache.sling.resourceresolver.impl.mapping;
  
- 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 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 java.io.File;
  import java.io.IOException;
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
@@@ -36,6 -34,6 +37,7 @@@ import java.text.ParseException
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
++import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.LinkedList;
@@@ -43,30 -41,62 +45,69 @@@ 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 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;
++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.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 extends AbstractMappingMapEntriesTest {
++public class MapEntriesTest {
+ 
+     private MapEntries mapEntries;
+ 
+     File vanityBloomFilterFile;
+ 
+     @Mock
+     private MapConfigurationProvider resourceResolverFactory;
+ 
+     @Mock
+     private BundleContext bundleContext;
  
- public class MapEntriesTest extends AbstractMappingMapEntriesTest {
+     @Mock
+     private Bundle bundle;
+ 
+     @Mock
+     private ResourceResolver resourceResolver;
+ 
+     @Mock
+     private EventAdmin eventAdmin;
+ 
+     private Map<String, Map<String, String>> aliasMap;
+ 
+     @SuppressWarnings({ "unchecked" })
+     @Before
+     public void setup() throws Exception {
+         MockitoAnnotations.initMocks(this);
  
-     List<MapConfigurationProvider.VanityPathConfig> getVanityPathConfigs() {
          final List<VanityPathConfig> configs = new ArrayList<>();
          configs.add(new VanityPathConfig("/libs/", false));
          configs.add(new VanityPathConfig("/libs/denied", true));
@@@ -80,12 -110,36 +121,37 @@@
          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);
+         final Field aliasMapField = MapEntries.class.getDeclaredField("aliasMap");
+         aliasMapField.setAccessible(true);
+ 
+         this.aliasMap = ( Map<String, Map<String, String>>) aliasMapField.get(mapEntries);
+     }
  
-         return configs;
+     @After
+     public void tearDown() throws Exception {
+         vanityBloomFilterFile.delete();
      }
  
+ 
 -    @Test
 -    public void test_simple_alias_support() {
 +    @Test(timeout = 1000)
 +    public void test_simple_alias_support() throws InterruptedException {
          Resource parent = mock(Resource.class);
          when(parent.getPath()).thenReturn("/parent");
  
diff --cc src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
index 1c56a47,1480397..3f36327
--- a/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
+++ b/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
@@@ -26,15 -26,12 +26,21 @@@ import org.apache.sling.api.wrappers.Va
  import org.apache.sling.resourceresolver.impl.SimpleValueMapImpl;
  import org.apache.sling.resourceresolver.impl.helper.RedirectResource;
  import org.apache.sling.resourceresolver.impl.mapping.MapEntry;
++<<<<<<< HEAD
 +import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider;
 +import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderConfiguration;
++=======
++>>>>>>> master
  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;
++<<<<<<< HEAD
 +import org.osgi.framework.BundleContext;
++=======
++>>>>>>> master
  
  import javax.servlet.http.HttpServletRequest;
  import java.lang.reflect.Field;
@@@ -75,9 -72,6 +81,12 @@@ public class MockTestUtil 
      }
  
      public static void checkInternalResource(Resource internal, String path) {
++<<<<<<< HEAD
 +//        assertThat("Not a Non Existing Resource", redirect, instanceOf(NonExistingResource.class));
 +//        NonExistingResource nonExistingResource = (NonExistingResource) redirect;
 +//        if(path != null) {
++=======
++>>>>>>> master
          assertEquals("Wrong Path for Resource", path, internal.getPath());
      }
  
@@@ -132,7 -126,7 +141,11 @@@
       * @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)
++<<<<<<< HEAD
 +     * @return
++=======
+      * @return Mock Resource able to handle addition of children later on
++>>>>>>> master
       */
      @SuppressWarnings("unchecked")
      public static Resource buildResource(String fullPath, Resource parent, ResourceResolver resourceResolver, ResourceProvider<?> provider, String... properties) {
@@@ -188,23 -182,57 +201,77 @@@
          return resource;
      }
  
++<<<<<<< HEAD
 +    public static Object callInaccessibleMethod(String methodName, Object target, Class paramsType, Object param) throws NoSuchMethodException {
 +        return callInaccessibleMethod(methodName, target, new Class[] {paramsType}, new Object[] {param});
 +    }
 +
 +    public static Object callInaccessibleMethod(String methodName, Object target, Class[] paramsTypes, Object[] params) throws NoSuchMethodException {
 +        if(paramsTypes != null && params != null) {
 +            if(params.length != paramsTypes.length) { throw new IllegalArgumentException("Number of Parameter Types and Values were not the same"); }
 +        } else {
 +            paramsTypes = null;
 +            params = null;
 +        }
 +        try {
 +            Method method = target.getClass().getDeclaredMethod(methodName, paramsTypes);
 +            method.setAccessible(true);
 +            return method.invoke(target, params);
 +        } catch(NoSuchMethodException e) {
 +            throw new UnsupportedOperationException("Failed to find method: " + methodName, e);
++=======
+     /**
+      * Calls a private method that has no parameter like a getter
+      *
+      * @param methodName Name of the method
+      * @param target Target instance
+      * @return Object that is returned from the method call
+      *
+      * @throws UnsupportedOperationException If the call failed because it method is not found, has no access or invocation failed
+      */
+     public static <T> T callInaccessibleMethod(String methodName, Class<T> returnType, Object target) {
+         return callInaccessibleMethod(methodName, returnType, target, new Class[] {}, new Object[] {});
+     }
+ 
+     /**
+      * Calls a private method that has one parameter like a setter method
+      *
+      * @param methodName Name of the method
+      * @param target Target instance
+      * @param paramsType Parameter Type which cannot be null
+      * @param param Parameter Value
+      * @return Object that is returned from the method call
+      *
+      * @throws UnsupportedOperationException If the call failed because it method is not found, has no access or invocation failed
+      */
+     public static <T> T callInaccessibleMethod(String methodName, Class<T> returnType, Object target, Class paramsType, Object param) {
+         return callInaccessibleMethod(methodName, returnType, target, new Class[] {paramsType}, new Object[] {param});
+     }
+ 
+     /**
+      * Calls a private method that has none or one parameter like a setter method
+      *
+      * ATTENTION: If parameter types of values is null then both are set to null. Also the length of the arrays must
+      * be the same
+      *
+      * @param methodName Name of the method
+      * @param target Target instance
+      * @param parameterTypes Parameter Types which must not be null
+      * @param parameters Parameter Values which must not be null
+      * @return Object that is returned from the method call
+      *
+      * @throws IllegalArgumentException If the parameter types and values do not match
+      * @throws UnsupportedOperationException If the call failed because it method is not found, has no access or invocation failed
+      */
+     public static <T> T callInaccessibleMethod(String methodName, Class<T> returnType, Object target, Class[] parameterTypes, Object[] parameters) {
+         if(parameterTypes != null && parameters != null) {
+             if(parameters.length != parameterTypes.length) { throw new IllegalArgumentException("Number of Parameter Types and Values were not the same"); }
+         } else {
+             throw new IllegalArgumentException("Parameter Type and Value Array cannot be null");
+         }
+         try {
+             return getInaccessibleMethod(methodName, returnType, target, parameterTypes).call(parameters);
++>>>>>>> master
          } catch (IllegalAccessException e) {
              throw new UnsupportedOperationException("Failed to access method: " + methodName, e);
          } catch (InvocationTargetException e) {
@@@ -212,26 -240,85 +279,108 @@@
          }
      }
  
++<<<<<<< HEAD
 +    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();
 +        }
 +    }
 +
 +    public static void setupStringInterpolationProvider(
 +        StringInterpolationProvider provider, StringInterpolationProviderConfiguration configuration, BundleContext bundleContext, final String[] placeholderValues
 +    ) throws NoSuchMethodException {
 +        when(configuration.place_holder_key_value_pairs()).thenReturn(placeholderValues);
 +        callInaccessibleMethod("activate", provider,
 +            new Class[] {BundleContext.class, StringInterpolationProviderConfiguration.class},
 +            new Object[] {bundleContext, configuration}
 +        );
++=======
+     public static <T> MethodWrapper<T> getInaccessibleMethod(String methodName, Class<T> returnType, Object target, Class...parameterTypes) {
+         return new MethodWrapper(methodName, returnType, target, parameterTypes);
+     }
+ 
+     public static class MethodWrapper<T> {
+         private Method method;
+         private Object target;
+ 
+         public MethodWrapper(String methodName, Class<T> returnType, Object target, Class[] parameterTypes) {
+             try {
+                 this.method = target.getClass().getDeclaredMethod(methodName, parameterTypes);
+                 this.method.setAccessible(true);
+                 this.target = target;
+                 if(returnType == null && !this.method.getReturnType().equals(Void.TYPE)) {
+                     throw new IllegalArgumentException("Return Type is null but method does not return Void but: " + this.method.getReturnType());
+                 }
+                 if(returnType != null && !returnType.isAssignableFrom(this.method.getReturnType())) {
+                     throw new IllegalArgumentException("Return Type is not assignable to: " + returnType + ", it returns this: " + this.method.getReturnType());
+                 }
+             } catch (NoSuchMethodException e) {
+                 throw new UnsupportedOperationException("Failed to find method: " + methodName, e);
+             }
+         }
+ 
+         public T call(Object...parameters) throws InvocationTargetException, IllegalAccessException {
+             return (T) method.invoke(target, parameters);
+         }
+     }
+ 
+     /**
+      * Sets the value of a private field
+      *
+      * @param fieldName Name of the field to be set
+      * @param target Target instance
+      * @param fieldValue Value to be set
+      *
+      * @throws UnsupportedOperationException If the call failed because it field is not found or has no access
+      */
+     public static void setInaccessibleField(String fieldName, Object target, Object fieldValue) throws NoSuchMethodException {
+         try {
+             getInaccessibleFieldWrapper(fieldName, target, Object.class).set(fieldValue);
+         } catch (IllegalAccessException e) {
+             throw new UnsupportedOperationException("Failed to access field: " + fieldName, e);
+         }
+     }
+ 
+     public static <T> T getInaccessibleField(String fieldName, Object target, Class<T> type) {
+         try {
+             return getInaccessibleFieldWrapper(fieldName, target, type).get();
+         } catch (IllegalAccessException e) {
+             throw new UnsupportedOperationException("Failed to access field: " + fieldName, e);
+         }
+     }
+ 
+     public static <T> FieldWrapper<T> getInaccessibleFieldWrapper(String fieldName, Object target, Class<T> type) {
+         try {
+             return new FieldWrapper(fieldName, target, type);
+         } catch (NoSuchFieldException e) {
+             throw new UnsupportedOperationException("Failed to find field: " + fieldName, e);
+         }
+     }
+ 
+     public static class FieldWrapper<T> {
+         private Field field;
+         private Object target;
+ 
+         public FieldWrapper(String fieldName, Object target, Class<T> type) throws NoSuchFieldException {
+             this.field = target.getClass().getDeclaredField(fieldName);
+             this.field.setAccessible(true);
+             this.target = target;
+         }
+ 
+         public void set(T parameter) throws IllegalAccessException {
+             field.set(target, parameter);
+         }
+ 
+         public T get() throws IllegalAccessException {
+             return (T) field.get(target);
+         }
++>>>>>>> master
      }
  
      /**
@@@ -243,6 -330,9 +392,12 @@@
          public List<Resource> getChildrenList();
      }
  
++<<<<<<< HEAD
++=======
+     /**
+      * Defines the Result of the Etc Mapping for easy testing
+      */
++>>>>>>> master
      public static class ExpectedEtcMapping {
          List<ExpectedEtcMapEntry> expectedEtcMapEntries = new ArrayList<>();
  


[sling-org-apache-sling-resourceresolver] 02/02: Migrated the Sling Interplation to Lang3 StrSubtitutor, adjusted the tests and fixed some merge problems

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 bdc07c81a082329627a8bb2ba14e0b1fde41531e
Author: Andreas Schaefer <sc...@iMac.local>
AuthorDate: Tue Jul 30 09:16:55 2019 -0700

    Migrated the Sling Interplation to Lang3 StrSubtitutor, adjusted the tests and fixed some merge problems
---
 README.md                                          |  54 ++++++++
 .../resourceresolver/impl/mapping/MapEntries.java  |  56 +-------
 .../impl/mapping/StringInterpolationProvider.java  |  68 +---------
 .../StringInterpolationProviderConfiguration.java  |  24 ++++
 .../mapping/StringInterpolationProviderImpl.java   | 114 ++++------------
 .../impl/EtcMappingResourceResolverTest.java       |  33 +----
 .../mapping/AbstractMappingMapEntriesTest.java     |  27 +---
 .../impl/mapping/EtcMappingMapEntriesTest.java     |  13 --
 .../impl/mapping/MapEntriesTest.java               |  20 +--
 .../impl/mapping/ResourceMapperImplTest.java       |   3 +-
 .../mapping/StringInterpolationMapEntriesTest.java |   4 +-
 .../StringInterpolationProviderImplTest.java       | 148 +++++++++++++++------
 .../sling/resourceresolver/util/MockTestUtil.java  |  87 ++++--------
 13 files changed, 262 insertions(+), 389 deletions(-)

diff --git a/README.md b/README.md
index 02bf7bc..00d8c1c 100644
--- a/README.md
+++ b/README.md
@@ -7,3 +7,57 @@
 This module is part of the [Apache Sling](https://sling.apache.org) project.
 
 This bundle provides the Resource Resolver and Resource Resolver Factory
+
+## ETC Map String Interpolation
+
+Setting up ETC Mappings (/etc/map) for different instances like dev, stage,
+qa and production was time consuming and error prone due to copy-n-paste
+errors. 
+As a new feature Sling now supports String Interpolation in the /etc/map.
+With it it is possible to create a single set of etc-mapping and then adjust
+the actual values of an instance by an OSGi configuration.
+By default a variable name is enclosed in **${}** with a **$** as escape
+character and no in-variable-substitution. All of that is configurable
+together with the actual value map.
+
+### Setup
+
+The Substitution Configuration can be found in the OSGi Configuration
+as **Apache Sling String Interpolation Provider**. The property **Placeholder
+Values** takes a list of **key=value** entries where each of them map a
+variable with its actual value.
+In our little introduction we add an entry of
+**phv.default.host.name=localhost**. Save the configuration for now.
+Before going on make sure that you know Mapping Location configuration
+in the OSGi configuration of **Apache Sling Resource Resolver Factory**.
+Now to to **composum** and go to that node. If it does not exist then create
+one. The mapping should look like this:
+* etc
+    * map
+        * http
+            * ${phv.fq.host.name}.8080
+            
+Opening the page **http://localhost:8080/starter/index.html** should
+work just fine.
+
+### Testing
+
+Now got back to the String Interpolation configuration and change the value
+to **qa.author.acme.com** and save it.
+
+For local testing open your **hosts** file (/etc/hosts on Unix) and add a
+line like this:
+```
+127.0.0.1 qa.author.acme.com
+```
+save it and test with `ping qa.author.acme.com` to make sure the name
+resolves.
+Now you should be able to open the same page with:
+**http://qa.author.acme.com/starter/index.html**.
+
+Now do the same with **phv.fq.host.name=staging.author.acme.com**.
+
+The String Interpolation works with any part of the etc-map tree.
+ 
+ 
+
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
index 2efa826..b13b25a 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
@@ -163,7 +163,8 @@ public class MapEntries implements
         this.resolveMapsMap = Collections.singletonMap(GLOBAL_LIST_KEY, (List<MapEntry>)Collections.EMPTY_LIST);
         this.mapMaps = Collections.<MapEntry> emptyList();
         this.vanityTargets = Collections.<String,List <String>>emptyMap();
-        this.aliasMap = Collections.emptyMap();
+        this.aliasMap = Collections.<String, Map<String, String>>emptyMap();
+        this.stringInterpolationProvider = stringInterpolationProvider;
 
         doInit();
 
@@ -355,7 +356,6 @@ public class MapEntries implements
      * Remove all aliases for the content path
      * @param contentPath The content path
      * @param path Optional sub path of the vanity path
-     * @param refreshed Flag if session needs refresh
      * @return {@code true} if a change happened
      */
     private boolean removeAlias(final String contentPath, final String path, final AtomicBoolean resolverRefreshed) {
@@ -713,7 +713,7 @@ public class MapEntries implements
 
     /**
      * Handles the change to any of the node properties relevant for vanity URL
-     * mappings. The {@link #MapEntries(ResourceResolverFactoryImpl, BundleContext, EventAdmin)}
+     * mappings. The {@link #MapEntries(MapConfigurationProvider, BundleContext, EventAdmin, StringInterpolationProvider)}
      * constructor makes sure the event listener is registered to only get
      * appropriate events.
      */
@@ -968,10 +968,7 @@ public class MapEntries implements
                 trailingSlash = true;
             }
             // Check for placeholders and replace if needed
-            StringInterpolationProvider.Check check = stringInterpolationProvider.hasPlaceholder(name);
-            if(check.getStatus() == StringInterpolationProvider.STATUS.found) {
-                name = stringInterpolationProvider.resolve(check);
-            }
+            name = stringInterpolationProvider.substitute(name);
 
             final String childPath = parentPath.concat(name);
 
@@ -1043,58 +1040,15 @@ public class MapEntries implements
      */
     private Map<String, Map<String, String>> loadAliases(final ResourceResolver resolver) {
         final Map<String, Map<String, String>> map = new ConcurrentHashMap<>();
-		String queryString = this.factory.isForceNoAliasTraversal() ? ALIAS_QUERY_NO_TRAVERSAL : ALIAS_QUERY_DEFAULT;
-		while (true){
-	        try {
+        final String queryString = "SELECT sling:alias FROM nt:base WHERE sling:alias IS NOT NULL";
 		        final Iterator<Resource> i = resolver.findResources(queryString, "sql");
 		        while (i.hasNext()) {
 		            final Resource resource = i.next();
 		            loadAlias(resource, map);
 		        }
-		        break;
-		} catch (SlingException e) {
-			Throwable cause = unwrapThrowable(e);
-			if (cause instanceof IllegalArgumentException && ALIAS_QUERY_NO_TRAVERSAL.equals(queryString)) {
-					log.debug(
-						"Expected index not available yet - will retry", e);
-					try {
-						TimeUnit.MILLISECONDS.sleep(getTraversalRetryInterval());
-					} catch (InterruptedException ex) {
-						log.warn("Interrupted while sleeping", ex);
-					}
-				} else if (cause instanceof ParseException) {
-				if (ALIAS_QUERY_NO_TRAVERSAL.equals(queryString)) {
-						log.warn("Traversal fail option set but query not accepted by queryengine, falling back to allowing traversal as queryengine might not support option", e);
-						queryString = ALIAS_QUERY_DEFAULT;
-					} else {
-						log.error("Queryengine couldn't parse query - interrupting loading of aliasmap",e);
-						break;
-					}
-					try {
-						TimeUnit.MILLISECONDS.sleep(getTraversalRetryInterval());
-					} catch (InterruptedException ex) {
-						log.warn("Interrupted while sleeping", ex);
-					}
-
-
-				} else {
-					log.error("QueryEngine not able to process query {} ", queryString, e);
-					break;
-				}
-		}
-		}
         return map;
     }
 
-    /**
-     * Extract root cause of exception
-     * @param e {@code Throwable} to be checked
-     * @return Root {@code Throwable}
-     */
-    private Throwable unwrapThrowable(Throwable e) {
-		return e.getCause() == null ? e : unwrapThrowable(e.getCause());
-	}
-
 	/**
      * Load alias given a resource
      */
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProvider.java b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProvider.java
index aa398e2..eabf1d2 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProvider.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProvider.java
@@ -31,70 +31,16 @@ import java.util.List;
  */
 public interface StringInterpolationProvider {
 
-    enum STATUS {found, unknown, none};
-
-    public static final String PLACEHOLDER_START_TOKEN = "${";
-    public static final String PLACEHOLDER_END_TOKEN = "}";
-    /**
-     * Checks if the given values contains a placeholder and if that placeholder is known
-     * @param value String to check
-     * @return Indicator if the given strings contains a known, unknown or no placeholders
-     */
-    Check hasPlaceholder(String value);
+    String DEFAULT_PREFIX = "${";
+    String DEFAULT_SUFFIX = "}";
+    char DEFAULT_ESCAPE_CHARACTER = '$';
+    boolean DEFAULT_IN_VARIABLE_SUBSTITUTION = false;
 
     /**
      * Replaces any placeholders with the replacement value
-     * ATTENTION: it is assumed that the string was checked and STATUS.found was returned.
-     * Any known placeholders will be replaced with an empty string
      *
-     * @param check Instance returned by has placeholder method
-     * @return Resolve string
+     * @param text Text to be substituted
+     * @return Substituted string
      */
-    String resolve(Check check);
-
-    public class Check {
-        private STATUS status;
-        private List<PlaceholderContext> placeholderContextList;
-        private String line;
-
-        public Check(STATUS status, String line, List<PlaceholderContext> placeholderContextList) {
-            this.status = status;
-            this.line = line;
-            this.placeholderContextList = placeholderContextList;
-        }
-
-        public STATUS getStatus() { return status; }
-
-        public List<PlaceholderContext> getPlaceholderContextList() {
-            return placeholderContextList;
-        }
-
-        public String getLine() {
-            return line;
-        }
-    }
-
-    public class PlaceholderContext {
-        private int start;
-        private int end;
-        private String name;
-
-        public PlaceholderContext(int start, int end, String name) {
-            this.start = start;
-            this.end = end;
-            this.name = name;
-        }
-
-        public int getStart() {
-            return start;
-        }
-
-        public int getEnd() {
-            return end;
-        }
-
-        public String getName() {
-            return name;
-        }
-    }
+    String substitute(String text);
 }
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderConfiguration.java b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderConfiguration.java
index 664669e..8b5f371 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderConfiguration.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderConfiguration.java
@@ -21,9 +21,33 @@ package org.apache.sling.resourceresolver.impl.mapping;
 import org.osgi.service.metatype.annotations.AttributeDefinition;
 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 
+import static org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider.DEFAULT_ESCAPE_CHARACTER;
+import static org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider.DEFAULT_IN_VARIABLE_SUBSTITUTION;
+import static org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider.DEFAULT_PREFIX;
+import static org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider.DEFAULT_SUFFIX;
+
 @ObjectClassDefinition(name = "Apache Sling String Interpolation Provider",
     description = "Configures the String Interpolation Provider and the location of its key/value pairs")
 public @interface StringInterpolationProviderConfiguration {
+
+    // Setup for the String Substitution
+    @AttributeDefinition(
+        name = "Substitution Prefix",
+        description = "The Prefix of the Variable to be replaced (default = '${')")
+    String substitution_prefix() default DEFAULT_PREFIX;
+    @AttributeDefinition(
+        name = "Substitution Suffix",
+        description = "The Suffix of the Variable to be replaced (deault = '}'")
+    String substitution_suffix() default DEFAULT_SUFFIX;
+    @AttributeDefinition(
+        name = "Substitution Escape Character",
+        description = "The Escape Character for Prefix or Suffix (default = '$'")
+    char substitution_escape_character() default DEFAULT_ESCAPE_CHARACTER;
+    @AttributeDefinition(
+        name = "Enable Substitution in Variables",
+        description = "Flag that indicates if substitution is allowed in Variables (default = false")
+    boolean substitution_in_variables() default DEFAULT_IN_VARIABLE_SUBSTITUTION;
+
     @AttributeDefinition(
         name = "Placeholder Values",
         description = "A list of key / value pairs separated by a equal (=) sign. " +
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImpl.java b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImpl.java
index 5150c83..f5bfab9 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImpl.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImpl.java
@@ -18,7 +18,7 @@
  */
 package org.apache.sling.resourceresolver.impl.mapping;
 
-import org.osgi.framework.BundleContext;
+import org.apache.commons.lang3.text.StrSubstitutor;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
@@ -30,9 +30,7 @@ import org.slf4j.LoggerFactory;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 @Designate(ocd = StringInterpolationProviderConfiguration.class)
@@ -75,9 +73,9 @@ public class StringInterpolationProviderImpl
         );
     }
 
-    private BundleContext bundleContext;
-    private StringInterpolationProviderConfiguration config = DEFAULT_CONFIG;
+//    private StringInterpolationProviderConfiguration config = DEFAULT_CONFIG;
     private Map<String, String> placeholderEntries = new HashMap<>();
+    private StrSubstitutor substitutor = new StrSubstitutor();
 
     // ---------- SCR Integration ---------------------------------------------
 
@@ -85,10 +83,14 @@ public class StringInterpolationProviderImpl
      * Activates this component (called by SCR before)
      */
     @Activate
-    protected void activate(final BundleContext bundleContext, final StringInterpolationProviderConfiguration config) {
-        this.bundleContext = bundleContext;
-        this.config = config;
-        for(String line: this.config.place_holder_key_value_pairs()) {
+    protected void activate(final StringInterpolationProviderConfiguration config) {
+        String prefix = config.substitution_prefix();
+        String suffix = config.substitution_suffix();
+        char escapeCharacter = config.substitution_escape_character();
+        boolean substitudeInVariables = config.substitution_in_variables();
+
+        String[] valueMap = config.place_holder_key_value_pairs();
+        for(String line: valueMap) {
             // Ignore no or empty lines
             if(line == null || line.isEmpty()) { continue; }
             // Ignore comments
@@ -104,15 +106,22 @@ public class StringInterpolationProviderImpl
             }
             placeholderEntries.put(line.substring(0, index), line.substring(index + 1));
         }
+
+        substitutor = new StrSubstitutor(
+            placeholderEntries,
+            prefix,
+            suffix,
+            escapeCharacter
+        );
+        substitutor.setEnableSubstitutionInVariables(substitudeInVariables);
     }
 
     /**
      * Modifies this component (called by SCR to update this component)
      */
     @Modified
-    protected void modified(final BundleContext bundleContext, final StringInterpolationProviderConfiguration config) {
-        this.deactivate();
-        this.activate(bundleContext, config);
+    protected void modified(final StringInterpolationProviderConfiguration config) {
+        this.activate(config);
     }
 
     /**
@@ -120,86 +129,11 @@ public class StringInterpolationProviderImpl
      */
     @Deactivate
     protected void deactivate() {
-        this.bundleContext = null;
-        this.config = DEFAULT_CONFIG;
-    }
-
-    @Override
-    public Check hasPlaceholder(String line) {
-        STATUS status = STATUS.none;
-        List<PlaceholderContext> placeholderContextList = parseLine(line);
-        for(PlaceholderContext placeholderContext: placeholderContextList) {
-            String name = placeholderContext.getName();
-            if(!placeholderEntries.containsKey(name)) {
-                logger.warn("Placeholder: '{}' not found in list of Placeholders: '{}'", name, placeholderEntries);
-                status = STATUS.unknown;
-            }
-            status = status == STATUS.none ? STATUS.found : status;
-        }
-        return new Check(status, line, placeholderContextList);
+        activate(DEFAULT_CONFIG);
     }
 
     @Override
-    public String resolve(Check check) {
-        if(check.getStatus() == STATUS.unknown) {
-            logger.warn("Line: '{}' contains unknown placeholders -> ignored", check.getLine());
-            return check.getLine();
-        }
-        List<PlaceholderContext> placeholderContextList = check.getPlaceholderContextList();
-        String line = check.getLine();
-        String answer = "";
-        if(placeholderContextList.isEmpty()) {
-            answer = line;
-        } else {
-            // The carret is the position in the source line. It is used to copy regular text
-            int carret = 0;
-            for (PlaceholderContext context : check.getPlaceholderContextList()) {
-                int start = context.getStart();
-                if(start > carret) {
-                    // There is text between the current position in the source and the next placeholder
-                    // so copy this into the target line
-                    String text = line.substring(carret, start);
-                    answer += text;
-                    carret += text.length();
-                }
-                int end = context.getEnd();
-                String name = context.getName();
-                String value = placeholderEntries.get(name);
-                // Add placeholder value into the target line
-                answer += value;
-                carret = carret + end - start + PLACEHOLDER_END_TOKEN.length();
-            }
-            if(carret < line.length()) {
-                // There is some text left after the last placeholder so copy this to the target line
-                answer += line.substring(carret);
-            }
-        }
-        return answer;
-    }
-
-    private List<PlaceholderContext> parseLine(String line) {
-        List<PlaceholderContext> answer = new ArrayList<>();
-        int index = -2;
-        if(line != null && !line.isEmpty()) {
-            while(true) {
-                index = line.indexOf(PLACEHOLDER_START_TOKEN, index + 1);
-                if (index < 0) {
-                    break;
-                }
-                int index2 = line.indexOf(PLACEHOLDER_END_TOKEN, index);
-                if(index2 < 0) {
-                    logger.warn("Given Line: '{}' contains an unclosed placeholder -> ignored", line);
-                    continue;
-                }
-                answer.add(
-                    new PlaceholderContext(
-                        index,
-                        index2,
-                        line.substring(index + PLACEHOLDER_START_TOKEN.length(), index2)
-                    )
-                );
-            }
-        }
-        return answer;
+    public String substitute(String text) {
+        return substitutor.replace(text);
     }
 }
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 9ae3f7d..6460ff5 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
@@ -23,11 +23,8 @@ 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;
-<<<<<<< HEAD
 import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderConfiguration;
 import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderImpl;
-=======
->>>>>>> master
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderTracker;
@@ -57,11 +54,9 @@ import static org.apache.sling.resourceresolver.util.MockTestUtil.callInaccessib
 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;
-<<<<<<< HEAD
 import static org.apache.sling.resourceresolver.util.MockTestUtil.setupStringInterpolationProvider;
-=======
->>>>>>> master
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
@@ -96,13 +91,9 @@ public class EtcMappingResourceResolverTest {
     @Mock
     ResourceProvider<?> resourceProvider;
 
-<<<<<<< HEAD
-    @Mock
     StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration;
 
     StringInterpolationProviderImpl stringInterpolationProvider = new StringInterpolationProviderImpl();
-=======
->>>>>>> master
     MapEntries mapEntries;
 
     File vanityBloomFilterFile;
@@ -131,10 +122,8 @@ public class EtcMappingResourceResolverTest {
         setInaccessibleField("resourceProviderTracker", activator, resourceProviderTracker);
         setInaccessibleField("resourceAccessSecurityTracker", activator, new ResourceAccessSecurityTracker());
         setInaccessibleField("bundleContext", activator, bundleContext);
-<<<<<<< HEAD
+        stringInterpolationProviderConfiguration = createStringInterpolationProviderConfiguration();
         setInaccessibleField("stringInterpolationProvider", activator, stringInterpolationProvider);
-=======
->>>>>>> master
         setInaccessibleField("mapRoot", activator, "/etc/map");
         setInaccessibleField("mapRootPrefix", activator, "/etc/map");
         setInaccessibleField("observationPaths", activator, new Path[] {new Path("/")});
@@ -145,11 +134,7 @@ public class EtcMappingResourceResolverTest {
         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
-<<<<<<< HEAD
-        callInaccessibleMethod("activate", commonFactory, BundleContext.class, bundleContext);
-=======
         callInaccessibleMethod("activate", null, commonFactory, BundleContext.class, bundleContext);
->>>>>>> master
         final Bundle usingBundle = mock(Bundle.class);
         resourceResolverFactory = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null);
         resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
@@ -163,8 +148,6 @@ public class EtcMappingResourceResolverTest {
         return new ArrayList<>();
     }
 
-<<<<<<< HEAD
-=======
     /**
      * Changes to the /etc/map in our tests are not taking effect until there is an Change Event issued
      *
@@ -174,7 +157,6 @@ public class EtcMappingResourceResolverTest {
      * @param path Path to the resource root to be refreshed
      * @param isExternal External flag of the ResourceChange event
      */
->>>>>>> master
     void refreshMapEntries(String path, boolean isExternal) {
         ((MapEntries) commonFactory.getMapEntries()).onChange(
             asList(
@@ -300,11 +282,10 @@ public class EtcMappingResourceResolverTest {
         checkInternalResource(resolvedResource, "/anecdotes/stories");
     }
 
-<<<<<<< HEAD
     @Test
     public void simple_node_string_interpolation() throws Exception {
         buildResource("${siv.one}", http, resourceResolver, resourceProvider,PROP_REDIRECT_EXTERNAL, "/content/simple-node");
-        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, bundleContext, new String[] {"siv.one=test-simple-node.80"});
+        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, new String[] {"siv.one=test-simple-node.80"});
 
         refreshMapEntries("/etc/map", true);
 
@@ -325,7 +306,7 @@ public class EtcMappingResourceResolverTest {
             PROP_REG_EXP, "${siv.one}/",
             PROP_REDIRECT_EXTERNAL, "/content/simple-match/"
         );
-        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, bundleContext, new String[] {"siv.one=test-simple-match.80"});
+        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, new String[] {"siv.one=test-simple-match.80"});
 
         refreshMapEntries("/etc/map", true);
 
@@ -335,11 +316,12 @@ public class EtcMappingResourceResolverTest {
         HttpServletRequest request = createRequestFromUrl("http://test-simple-match:80/");
         Resource resolvedResource = resourceResolver.resolve(request, "/");
         checkRedirectResource(resolvedResource, "/content/simple-match/", 302);
-=======
+    }
+
     /**
      * ATTENTION: this tests showcases an erroneous condition of an endless circular mapping in the /etc/map. When
      * this test passes this condition is present. After a fix this test must be adjusted.
-     * 
+     *
      * This confirms an issue with the Etc Mapping where a mapping from a node to a child node (here / to /content)
      * ends up in a endless circular mapping.
      * The only way to recover from this is to go to the OSGi console and change the /etc/map path in the Resource
@@ -368,6 +350,5 @@ public class EtcMappingResourceResolverTest {
 
         resolvedResource = resourceResolver.resolve(request, "/content/content/test.html");
         checkRedirectResource(resolvedResource, "/content/content/content/test.html", 302);
->>>>>>> master
     }
 }
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 e7129da..44e04a9 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
@@ -46,10 +46,8 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.Semaphore;
 
-<<<<<<< HEAD
+import static org.apache.sling.resourceresolver.util.MockTestUtil.createStringInterpolationProviderConfiguration;
 import static org.apache.sling.resourceresolver.util.MockTestUtil.setupStringInterpolationProvider;
-=======
->>>>>>> master
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyMap;
@@ -82,13 +80,9 @@ public abstract class AbstractMappingMapEntriesTest {
     @Mock
     ResourceResolver resourceResolver;
 
-<<<<<<< HEAD
-    @Mock
     StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration;
 
     StringInterpolationProviderImpl stringInterpolationProvider = new StringInterpolationProviderImpl();
-=======
->>>>>>> master
     MapEntries mapEntries;
 
     File vanityBloomFilterFile;
@@ -112,10 +106,6 @@ public abstract class AbstractMappingMapEntriesTest {
         when(resourceResolverFactory.isVanityPathEnabled()).thenReturn(true);
         when(resourceResolverFactory.getVanityPathConfig()).thenReturn(configs);
         when(resourceResolverFactory.isOptimizeAliasResolutionEnabled()).thenReturn(true);
-<<<<<<< HEAD
-        when(resourceResolverFactory.isForceNoAliasTraversal()).thenReturn(true);
-=======
->>>>>>> master
         when(resourceResolverFactory.getObservationPaths()).thenReturn(new Path[] {new Path("/")});
         when(resourceResolverFactory.getMapRoot()).thenReturn(MapEntries.DEFAULT_MAP_ROOT);
         when(resourceResolverFactory.getMaxCachedVanityPathEntries()).thenReturn(-1L);
@@ -126,12 +116,9 @@ public abstract class AbstractMappingMapEntriesTest {
         map = setupEtcMapResource("/etc", "map");
         http = setupEtcMapResource("http", map);
 
-<<<<<<< HEAD
-        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, bundleContext, new String[] {});
+        stringInterpolationProviderConfiguration = createStringInterpolationProviderConfiguration();
+        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, new String[] {});
         mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
-=======
-        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin);
->>>>>>> master
 
         final Field aliasMapField = MapEntries.class.getDeclaredField("aliasMap");
         aliasMapField.setAccessible(true);
@@ -204,11 +191,7 @@ public abstract class AbstractMappingMapEntriesTest {
         return resource;
     }
 
-<<<<<<< HEAD
-    MapEntriesTest.DataFuture createDataFuture(ExecutorService pool, final MapEntries mapEntries) {
-=======
     DataFuture createDataFuture(ExecutorService pool, final MapEntries mapEntries) {
->>>>>>> master
 
         Future<Iterator<?>> future = pool.submit(new Callable<Iterator<?>>() {
             @Override
@@ -216,11 +199,7 @@ public abstract class AbstractMappingMapEntriesTest {
                 return mapEntries.getResolveMapsIterator("http/localhost.8080/target/justVanityPath");
             }
         });
-<<<<<<< HEAD
-        return new MapEntriesTest.DataFuture(future);
-=======
         return new DataFuture(future);
->>>>>>> master
     }
 
     void simulateSomewhatSlowSessionOperation(final Semaphore sessionLock) throws InterruptedException {
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 bfede24..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
@@ -16,7 +16,6 @@
  */
 package org.apache.sling.resourceresolver.impl.mapping;
 
-<<<<<<< HEAD
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.path.Path;
@@ -51,14 +50,6 @@ 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 static org.apache.sling.resourceresolver.impl.ResourceResolverImpl.PROP_REDIRECT_INTERNAL;
-import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
-
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.resourceresolver.util.MockTestUtil.ExpectedEtcMapping;
-import org.junit.Test;
->>>>>>> master
 
 /**
  * These tests are for the /etc/map setup of the Map Entries when
@@ -136,7 +127,6 @@ public class EtcMappingMapEntriesTest extends AbstractMappingMapEntriesTest {
             .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());
-<<<<<<< HEAD
 
         // Not really an etc-map resource but it is good for now
         final Resource test = setupEtcMapResource("/scripts", "test");
@@ -258,7 +248,4 @@ public class EtcMappingMapEntriesTest extends AbstractMappingMapEntriesTest {
 //        Resource mappedResource = resResolver.resolve(request, "/b.html");
 //        String path = mappedResource.getPath();
 //    }
-=======
-    }
->>>>>>> master
 }
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 db072c3..fc6691c 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
@@ -21,11 +21,9 @@ 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;
 
@@ -33,11 +31,9 @@ import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -45,22 +41,16 @@ 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.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;
@@ -70,7 +60,6 @@ import org.apache.sling.resourceresolver.impl.mapping.MapConfigurationProvider.V
 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;
@@ -80,7 +69,7 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.service.event.EventAdmin;
 
-public class MapEntriesTest {
+public class MapEntriesTest extends AbstractMappingMapEntriesTest {
 
     private MapEntries mapEntries;
 
@@ -129,7 +118,6 @@ public class MapEntriesTest {
         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);
@@ -137,7 +125,7 @@ public class MapEntriesTest {
         when(resourceResolver.findResources(anyString(), eq("sql"))).thenReturn(
                 Collections.<Resource> emptySet().iterator());
 
-        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin);
+        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
         final Field aliasMapField = MapEntries.class.getDeclaredField("aliasMap");
         aliasMapField.setAccessible(true);
 
@@ -150,8 +138,8 @@ public class MapEntriesTest {
     }
 
 
-    @Test(timeout = 1000)
-    public void test_simple_alias_support() throws InterruptedException {
+    @Test
+    public void test_simple_alias_support() {
         Resource parent = mock(Resource.class);
         when(parent.getPath()).thenReturn("/parent");
 
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 58263b6..479ff0e 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
@@ -81,7 +81,8 @@ public class ResourceMapperImplTest {
 
         ctx.registerInjectActivateService(new ServiceUserMapperImpl());
         ctx.registerInjectActivateService(new ResourceAccessSecurityTracker());
-        
+        ctx.registerInjectActivateService(new StringInterpolationProviderImpl());
+
         InMemoryResourceProvider resourceProvider = new InMemoryResourceProvider();
         resourceProvider.putResource("/"); // root
         resourceProvider.putResource("/here"); // regular page
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 680dd36..9aa5d05 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
@@ -33,7 +33,7 @@ public class StringInterpolationMapEntriesTest extends AbstractMappingMapEntries
     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/simple-node");
-        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, bundleContext, new String[] {"siv.one=test-simple-node"});
+        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, new String[] {"siv.one=test-simple-node"});
 
         mapEntries.doInit();
         ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-node/", "/content/simple-node/");
@@ -47,7 +47,7 @@ public class StringInterpolationMapEntriesTest extends AbstractMappingMapEntries
             PROP_REG_EXP, "${siv.one}/",
             PROP_REDIRECT_EXTERNAL, "/content/simple-match/"
         );
-        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, bundleContext, new String[] {"siv.one=test-simple-match"});
+        setupStringInterpolationProvider(stringInterpolationProvider, stringInterpolationProviderConfiguration, new String[] {"siv.one=test-simple-match"});
 
         mapEntries.doInit();
         ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-match/", "/content/simple-match/");
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImplTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImplTest.java
index 212c928..3c0b461 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImplTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImplTest.java
@@ -18,16 +18,18 @@
  */
 package org.apache.sling.resourceresolver.impl.mapping;
 
+import org.apache.commons.lang3.text.StrSubstitutor;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.osgi.framework.BundleContext;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.when;
-import static org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider.PLACEHOLDER_START_TOKEN;
-import static org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider.PLACEHOLDER_END_TOKEN;
 
 public class StringInterpolationProviderImplTest {
 
@@ -41,6 +43,19 @@ public class StringInterpolationProviderImplTest {
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(stringInterpolationProviderConfiguration.substitution_prefix()).thenReturn("${");
+        when(stringInterpolationProviderConfiguration.substitution_suffix()).thenReturn("}");
+        when(stringInterpolationProviderConfiguration.substitution_escape_character()).thenReturn('$');
+        when(stringInterpolationProviderConfiguration.substitution_in_variables()).thenReturn(false);
+    }
+
+    @Test
+    public void test_strsubstitutor() {
+        Map<String,String> values = new HashMap<>();
+        values.put("one", "two");
+        StrSubstitutor substitutor = new StrSubstitutor(values, "${", "}", '$');
+        String substitude = substitutor.replace("${one}");
+        assertEquals("Wrong Replacement", "two", substitude);
     }
 
     @Test
@@ -50,13 +65,11 @@ public class StringInterpolationProviderImplTest {
         );
 
         StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
-        placeholderProvider.activate(bundleContext, stringInterpolationProviderConfiguration);
+        placeholderProvider.activate(stringInterpolationProviderConfiguration);
 
-        String line = PLACEHOLDER_START_TOKEN + "one" + PLACEHOLDER_END_TOKEN;
-        StringInterpolationProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", StringInterpolationProvider.STATUS.found, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "two", resolved);
+        String line = "${one}";
+        String substituted = placeholderProvider.substitute(line);
+        assertEquals("Wrong resolved line", "two", substituted);
     }
 
     @Test
@@ -65,13 +78,11 @@ public class StringInterpolationProviderImplTest {
             new String[] { "one=two"}
         );
         StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
-        placeholderProvider.activate(bundleContext, stringInterpolationProviderConfiguration);
+        placeholderProvider.activate(stringInterpolationProviderConfiguration);
 
-        String line = "Here is " + PLACEHOLDER_START_TOKEN + "one" + PLACEHOLDER_END_TOKEN + ", too";
-        StringInterpolationProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", StringInterpolationProvider.STATUS.found, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "Here is two, too", resolved);
+        String line = "Here is ${one}, too";
+        String substituted = placeholderProvider.substitute(line);
+        assertEquals("Wrong resolved line", "Here is two, too", substituted);
     }
 
     @Test
@@ -80,13 +91,11 @@ public class StringInterpolationProviderImplTest {
             new String[] { "one=two", "three=four"}
         );
         StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
-        placeholderProvider.activate(bundleContext, stringInterpolationProviderConfiguration);
+        placeholderProvider.activate(stringInterpolationProviderConfiguration);
 
-        String line = PLACEHOLDER_START_TOKEN + "one" + PLACEHOLDER_END_TOKEN + " with another " + PLACEHOLDER_START_TOKEN + "three" + PLACEHOLDER_END_TOKEN;
-        StringInterpolationProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", StringInterpolationProvider.STATUS.found, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "two with another four", resolved);
+        String line = "${one} with another ${three}";
+        String substituted = placeholderProvider.substitute(line);
+        assertEquals("Wrong resolved line", "two with another four", substituted);
     }
 
     @Test
@@ -96,13 +105,11 @@ public class StringInterpolationProviderImplTest {
         );
 
         StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
-        placeholderProvider.activate(bundleContext, stringInterpolationProviderConfiguration);
+        placeholderProvider.activate(stringInterpolationProviderConfiguration);
 
-        String line = "Here comes " + PLACEHOLDER_START_TOKEN + "one" + PLACEHOLDER_END_TOKEN + " with another " + PLACEHOLDER_START_TOKEN + "three" + PLACEHOLDER_END_TOKEN + " equals " + PLACEHOLDER_START_TOKEN + "five" + PLACEHOLDER_END_TOKEN + ", horray!";
-        StringInterpolationProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", StringInterpolationProvider.STATUS.found, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "Here comes two with another four equals six, horray!", resolved);
+        String line = "Here comes ${one} with another ${three} equals ${five}, horray!";
+        String substituted = placeholderProvider.substitute(line);
+        assertEquals("Wrong resolved line", "Here comes two with another four equals six, horray!", substituted);
     }
 
     @Test
@@ -112,13 +119,11 @@ public class StringInterpolationProviderImplTest {
         );
 
         StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
-        placeholderProvider.activate(bundleContext, stringInterpolationProviderConfiguration);
+        placeholderProvider.activate(stringInterpolationProviderConfiguration);
 
         String line = "Here comes is a text with no placeholders!";
-        StringInterpolationProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", StringInterpolationProvider.STATUS.none, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "Here comes is a text with no placeholders!", resolved);
+        String substituted = placeholderProvider.substitute(line);
+        assertEquals("Wrong resolved line", "Here comes is a text with no placeholders!", substituted);
     }
 
     @Test
@@ -128,13 +133,11 @@ public class StringInterpolationProviderImplTest {
         );
 
         StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
-        placeholderProvider.activate(bundleContext, stringInterpolationProviderConfiguration);
+        placeholderProvider.activate(stringInterpolationProviderConfiguration);
 
-        String line = "Here comes " + PLACEHOLDER_START_TOKEN + "unkown" + PLACEHOLDER_END_TOKEN + " placeholders!";
-        StringInterpolationProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", StringInterpolationProvider.STATUS.unknown, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "Here comes ${unkown} placeholders!", resolved);
+        String line = "Here comes ${unkown} placeholders!";
+        String substituted = placeholderProvider.substitute(line);
+        assertEquals("Wrong resolved line", "Here comes ${unkown} placeholders!", substituted);
     }
 
     @Test
@@ -144,12 +147,71 @@ public class StringInterpolationProviderImplTest {
         );
 
         StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
-        placeholderProvider.activate(bundleContext, stringInterpolationProviderConfiguration);
+        placeholderProvider.activate(stringInterpolationProviderConfiguration);
+
+        String line = "${siv.one}/";
+        String substituted = placeholderProvider.substitute(line);
+        assertEquals("Wrong resolved line", "test-value/", substituted);
+    }
+
+    @Test
+    public void test_different_suffix_prefix() {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
+            new String[] { "test-me.one=hello"}
+        );
+        when(stringInterpolationProviderConfiguration.substitution_prefix()).thenReturn("{{");
+        when(stringInterpolationProviderConfiguration.substitution_suffix()).thenReturn("}}");
+
+        StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
+        placeholderProvider.activate(stringInterpolationProviderConfiguration);
+
+        String line = "a-{{test-me.one}}-a";
+        String substituted = placeholderProvider.substitute(line);
+        assertEquals("Wrong resolved line", "a-hello-a", substituted);
+    }
+
+    @Test
+    public void test_escape_character() {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
+            new String[] { "one=two"}
+        );
+        when(stringInterpolationProviderConfiguration.substitution_escape_character()).thenReturn('\\');
+
+        StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
+        placeholderProvider.activate(stringInterpolationProviderConfiguration);
+
+        String line = "\\${one}=${one}";
+        String substituted = placeholderProvider.substitute(line);
+        assertEquals("Wrong resolved line", "${one}=two", substituted);
+    }
+
+    @Test
+    public void test_in_variables_substitution() {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
+            new String[] { "one=two", "two=three"}
+        );
+        when(stringInterpolationProviderConfiguration.substitution_in_variables()).thenReturn(true);
+
+        StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
+        placeholderProvider.activate(stringInterpolationProviderConfiguration);
+
+        String line = "${${one}}";
+        String substituted = placeholderProvider.substitute(line);
+        assertEquals("Wrong resolved line", "three", substituted);
+    }
+
+    @Test
+    public void test_in_variables_substitution2() {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
+            new String[] { "one=two", "onetwo=three"}
+        );
+        when(stringInterpolationProviderConfiguration.substitution_in_variables()).thenReturn(true);
+
+        StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
+        placeholderProvider.activate(stringInterpolationProviderConfiguration);
 
-        String line = PLACEHOLDER_START_TOKEN + "siv.one" + PLACEHOLDER_END_TOKEN + "/";
-        StringInterpolationProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", StringInterpolationProvider.STATUS.found, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "test-value/", resolved);
+        String line = "${one${one}}";
+        String substituted = placeholderProvider.substitute(line);
+        assertEquals("Wrong resolved line", "three", substituted);
     }
 }
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 3f36327..d40bcf6 100644
--- a/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
+++ b/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
@@ -26,21 +26,15 @@ import org.apache.sling.api.wrappers.ValueMapDecorator;
 import org.apache.sling.resourceresolver.impl.SimpleValueMapImpl;
 import org.apache.sling.resourceresolver.impl.helper.RedirectResource;
 import org.apache.sling.resourceresolver.impl.mapping.MapEntry;
-<<<<<<< HEAD
 import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider;
 import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderConfiguration;
-=======
->>>>>>> master
 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;
-<<<<<<< HEAD
 import org.osgi.framework.BundleContext;
-=======
->>>>>>> master
 
 import javax.servlet.http.HttpServletRequest;
 import java.lang.reflect.Field;
@@ -48,10 +42,15 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 
 import static java.util.Arrays.asList;
+import static org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider.DEFAULT_ESCAPE_CHARACTER;
+import static org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider.DEFAULT_IN_VARIABLE_SUBSTITUTION;
+import static org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider.DEFAULT_PREFIX;
+import static org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider.DEFAULT_SUFFIX;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
@@ -81,12 +80,6 @@ public class MockTestUtil {
     }
 
     public static void checkInternalResource(Resource internal, String path) {
-<<<<<<< HEAD
-//        assertThat("Not a Non Existing Resource", redirect, instanceOf(NonExistingResource.class));
-//        NonExistingResource nonExistingResource = (NonExistingResource) redirect;
-//        if(path != null) {
-=======
->>>>>>> master
         assertEquals("Wrong Path for Resource", path, internal.getPath());
     }
 
@@ -141,11 +134,7 @@ public class MockTestUtil {
      * @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)
-<<<<<<< HEAD
-     * @return
-=======
      * @return Mock Resource able to handle addition of children later on
->>>>>>> master
      */
     @SuppressWarnings("unchecked")
     public static Resource buildResource(String fullPath, Resource parent, ResourceResolver resourceResolver, ResourceProvider<?> provider, String... properties) {
@@ -201,25 +190,6 @@ public class MockTestUtil {
         return resource;
     }
 
-<<<<<<< HEAD
-    public static Object callInaccessibleMethod(String methodName, Object target, Class paramsType, Object param) throws NoSuchMethodException {
-        return callInaccessibleMethod(methodName, target, new Class[] {paramsType}, new Object[] {param});
-    }
-
-    public static Object callInaccessibleMethod(String methodName, Object target, Class[] paramsTypes, Object[] params) throws NoSuchMethodException {
-        if(paramsTypes != null && params != null) {
-            if(params.length != paramsTypes.length) { throw new IllegalArgumentException("Number of Parameter Types and Values were not the same"); }
-        } else {
-            paramsTypes = null;
-            params = null;
-        }
-        try {
-            Method method = target.getClass().getDeclaredMethod(methodName, paramsTypes);
-            method.setAccessible(true);
-            return method.invoke(target, params);
-        } catch(NoSuchMethodException e) {
-            throw new UnsupportedOperationException("Failed to find method: " + methodName, e);
-=======
     /**
      * Calls a private method that has no parameter like a getter
      *
@@ -271,7 +241,6 @@ public class MockTestUtil {
         }
         try {
             return getInaccessibleMethod(methodName, returnType, target, parameterTypes).call(parameters);
->>>>>>> master
         } catch (IllegalAccessException e) {
             throw new UnsupportedOperationException("Failed to access method: " + methodName, e);
         } catch (InvocationTargetException e) {
@@ -279,28 +248,6 @@ public class MockTestUtil {
         }
     }
 
-<<<<<<< HEAD
-    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();
-        }
-    }
-
-    public static void setupStringInterpolationProvider(
-        StringInterpolationProvider provider, StringInterpolationProviderConfiguration configuration, BundleContext bundleContext, final String[] placeholderValues
-    ) throws NoSuchMethodException {
-        when(configuration.place_holder_key_value_pairs()).thenReturn(placeholderValues);
-        callInaccessibleMethod("activate", provider,
-            new Class[] {BundleContext.class, StringInterpolationProviderConfiguration.class},
-            new Object[] {bundleContext, configuration}
-        );
-=======
     public static <T> MethodWrapper<T> getInaccessibleMethod(String methodName, Class<T> returnType, Object target, Class...parameterTypes) {
         return new MethodWrapper(methodName, returnType, target, parameterTypes);
     }
@@ -363,6 +310,26 @@ public class MockTestUtil {
         }
     }
 
+    public static StringInterpolationProviderConfiguration createStringInterpolationProviderConfiguration() {
+        StringInterpolationProviderConfiguration answer = mock(StringInterpolationProviderConfiguration.class);
+        when(answer.substitution_prefix()).thenReturn(DEFAULT_PREFIX);
+        when(answer.substitution_suffix()).thenReturn(DEFAULT_SUFFIX);
+        when(answer.substitution_escape_character()).thenReturn(DEFAULT_ESCAPE_CHARACTER);
+        when(answer.substitution_in_variables()).thenReturn(DEFAULT_IN_VARIABLE_SUBSTITUTION);
+        when(answer.place_holder_key_value_pairs()).thenReturn(new String[] {});
+        return answer;
+    }
+
+    public static void setupStringInterpolationProvider(
+        StringInterpolationProvider provider, StringInterpolationProviderConfiguration configuration, final String[] placeholderValues
+    ) {
+        when(configuration.place_holder_key_value_pairs()).thenReturn(placeholderValues);
+        callInaccessibleMethod("activate", Void.TYPE, provider,
+            new Class[] {StringInterpolationProviderConfiguration.class},
+            new Object[] {configuration}
+        );
+    }
+
     public static class FieldWrapper<T> {
         private Field field;
         private Object target;
@@ -380,7 +347,6 @@ public class MockTestUtil {
         public T get() throws IllegalAccessException {
             return (T) field.get(target);
         }
->>>>>>> master
     }
 
     /**
@@ -392,12 +358,9 @@ public class MockTestUtil {
         public List<Resource> getChildrenList();
     }
 
-<<<<<<< HEAD
-=======
     /**
      * Defines the Result of the Etc Mapping for easy testing
      */
->>>>>>> master
     public static class ExpectedEtcMapping {
         List<ExpectedEtcMapEntry> expectedEtcMapEntries = new ArrayList<>();