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:11 UTC

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

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<>();