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

[sling-org-apache-sling-resourceresolver] 02/02: Rename the PlaceHolderProvider to StringInterpolationProvider and created a test suite to test the String Interpolation Provider with Map Entries

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 511d33d2c65caa3495f0ec803d6816c002735c6e
Author: Andreas Schaefer <sc...@me.com>
AuthorDate: Wed Jul 25 15:18:58 2018 -0700

    Rename the PlaceHolderProvider to StringInterpolationProvider and created a test suite to test the String Interpolation Provider with Map Entries
---
 pom.xml                                            |   3 +
 .../impl/CommonResourceResolverFactoryImpl.java    |   2 +-
 .../impl/ResourceResolverFactoryActivator.java     |   8 +-
 .../resourceresolver/impl/mapping/MapEntries.java  |  14 +-
 ...vider.java => StringInterpolationProvider.java} |   8 +-
 ... StringInterpolationProviderConfiguration.java} |   6 +-
 ...l.java => StringInterpolationProviderImpl.java} |  28 +--
 .../impl/mapping/MapEntriesTest.java               |  21 ++-
 .../impl/mapping/PlaceholderProviderImplTest.java  | 139 ---------------
 .../mapping/StringInterpolationMapEntriesTest.java | 195 +++++++++++++++++++++
 .../StringInterpolationProviderImplTest.java       | 155 ++++++++++++++++
 11 files changed, 398 insertions(+), 181 deletions(-)

diff --git a/pom.xml b/pom.xml
index e0c613c..5466e8a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -73,6 +73,9 @@
                             javax.jcr;resolution:=optional,
                             *
                         </Import-Package>
+                        <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,
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java b/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java
index 3289098..d393cd8 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/CommonResourceResolverFactoryImpl.java
@@ -322,7 +322,7 @@ public class CommonResourceResolverFactoryImpl implements ResourceResolverFactor
         }
         // set up the map entries from configuration
         try {
-            mapEntries = new MapEntries(this, bundleContext, this.activator.getEventAdmin(), this.activator.getPlaceholderProvider());
+            mapEntries = new MapEntries(this, bundleContext, this.activator.getEventAdmin(), this.activator.getStringInterpolationProvider());
         } catch (final Exception e) {
             logger.error("activate: Cannot access repository, failed setting up Mapping Support", e);
         }
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
index 371d5a3..478be4a 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java
@@ -39,7 +39,7 @@ 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.PlaceholderProvider;
+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;
@@ -111,7 +111,7 @@ public class ResourceResolverFactoryActivator {
 
     /** Event admin. */
     @Reference
-    PlaceholderProvider placeholderProvider;
+    StringInterpolationProvider stringInterpolationProvider;
 
     /** Service User Mapper */
     @Reference
@@ -158,8 +158,8 @@ public class ResourceResolverFactoryActivator {
         return this.eventAdmin;
     }
 
-    public PlaceholderProvider getPlaceholderProvider() {
-        return placeholderProvider;
+    public StringInterpolationProvider getStringInterpolationProvider() {
+        return stringInterpolationProvider;
     }
 
     /**
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 7877171..89fa39b 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
@@ -55,7 +55,6 @@ 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;
@@ -69,7 +68,6 @@ import org.apache.sling.resourceresolver.impl.ResourceResolverImpl;
 import org.apache.sling.resourceresolver.impl.mapping.MapConfigurationProvider.VanityPathConfig;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.event.Event;
 import org.osgi.service.event.EventAdmin;
@@ -153,10 +151,10 @@ public class MapEntries implements
 
     private Thread aliasTraversal = null;
 
-    private PlaceholderProvider placeholderProvider;
+    private StringInterpolationProvider stringInterpolationProvider;
 
     @SuppressWarnings({ "unchecked" })
-    public MapEntries(final MapConfigurationProvider factory, final BundleContext bundleContext, final EventAdmin eventAdmin, final PlaceholderProvider placeholderProvider)
+    public MapEntries(final MapConfigurationProvider factory, final BundleContext bundleContext, final EventAdmin eventAdmin, final StringInterpolationProvider stringInterpolationProvider)
         throws LoginException, IOException {
 
     	this.resolver = factory.getServiceResourceResolver(factory.getServiceUserAuthenticationInfo("mapping"));
@@ -167,7 +165,7 @@ public class MapEntries implements
         this.mapMaps = Collections.<MapEntry> emptyList();
         this.vanityTargets = Collections.<String,List <String>>emptyMap();
         this.aliasMap = Collections.emptyMap();
-        this.placeholderProvider = placeholderProvider;
+        this.stringInterpolationProvider = stringInterpolationProvider;
 
         doInit();
 
@@ -991,9 +989,9 @@ public class MapEntries implements
                 trailingSlash = true;
             }
             // Check for placeholders and replace if needed
-            PlaceholderProvider.Check check = placeholderProvider.hasPlaceholder(name);
-            if(check.getStatus() == PlaceholderProvider.STATUS.found) {
-                name = placeholderProvider.resolve(check);
+            StringInterpolationProvider.Check check = stringInterpolationProvider.hasPlaceholder(name);
+            if(check.getStatus() == StringInterpolationProvider.STATUS.found) {
+                name = stringInterpolationProvider.resolve(check);
             }
 
             final String childPath = parentPath.concat(name);
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/PlaceholderProvider.java b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProvider.java
similarity index 91%
rename from src/main/java/org/apache/sling/resourceresolver/impl/mapping/PlaceholderProvider.java
rename to src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProvider.java
index 95c01d7..aa398e2 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/PlaceholderProvider.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProvider.java
@@ -25,16 +25,16 @@ import java.util.List;
  * that depend on the environment like host names / ports for dev, test,
  * qa, staging, prod systems
  *
- * Placeholders are enclosed in two opening/closing round brackets: {{replaceMe}}
+ * Placeholders are enclosed in Starting and Ending Delimiters (see PLACEHOLDER_START/END_TOKEN)
  * The name of the placeholder can contain any character except opening or closing
  * brackets (no nesting).
  */
-public interface PlaceholderProvider {
+public interface StringInterpolationProvider {
 
     enum STATUS {found, unknown, none};
 
-    public static final String PLACEHOLDER_START_TOKEN = "{{";
-    public static final String PLACEHOLDER_END_TOKEN = "}}";
+    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
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/PlaceholderProviderConfiguration.java b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderConfiguration.java
similarity index 87%
rename from src/main/java/org/apache/sling/resourceresolver/impl/mapping/PlaceholderProviderConfiguration.java
rename to src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderConfiguration.java
index eea215c..664669e 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/PlaceholderProviderConfiguration.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderConfiguration.java
@@ -21,9 +21,9 @@ package org.apache.sling.resourceresolver.impl.mapping;
 import org.osgi.service.metatype.annotations.AttributeDefinition;
 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 
-@ObjectClassDefinition(name = "Apache Sling Placeholder Provider",
-    description = "Configures the Placeholder Provider and the location of its key/value pairs")
-public @interface PlaceholderProviderConfiguration {
+@ObjectClassDefinition(name = "Apache Sling String Interpolation Provider",
+    description = "Configures the String Interpolation Provider and the location of its key/value pairs")
+public @interface StringInterpolationProviderConfiguration {
     @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/PlaceholderProviderImpl.java b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImpl.java
similarity index 89%
rename from src/main/java/org/apache/sling/resourceresolver/impl/mapping/PlaceholderProviderImpl.java
rename to src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImpl.java
index 836a3cf..5150c83 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/PlaceholderProviderImpl.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImpl.java
@@ -35,15 +35,15 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-@Designate(ocd = PlaceholderProviderConfiguration.class)
-@Component(name = "org.apache.sling.resourceresolver.impl.mapping.PlaceholderProvider")
-public class PlaceholderProviderImpl
-    implements PlaceholderProvider
+@Designate(ocd = StringInterpolationProviderConfiguration.class)
+@Component(name = "org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider")
+public class StringInterpolationProviderImpl
+    implements StringInterpolationProvider
 {
     /** Logger. */
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
-    public static PlaceholderProviderConfiguration DEFAULT_CONFIG;
+    public static StringInterpolationProviderConfiguration DEFAULT_CONFIG;
 
     static {
         final InvocationHandler handler = new InvocationHandler() {
@@ -51,12 +51,12 @@ public class PlaceholderProviderImpl
             @Override
             public Object invoke(final Object obj, final Method calledMethod, final Object[] args)
                 throws Throwable {
-                if ( calledMethod.getDeclaringClass().isAssignableFrom(PlaceholderProviderConfiguration.class) ) {
+                if ( calledMethod.getDeclaringClass().isAssignableFrom(StringInterpolationProviderConfiguration.class) ) {
                     return calledMethod.getDefaultValue();
                 }
                 if ( calledMethod.getDeclaringClass() == Object.class ) {
                     if ( calledMethod.getName().equals("toString") && (args == null || args.length == 0) ) {
-                        return "Generated @" + PlaceholderProviderConfiguration.class.getName() + " instance";
+                        return "Generated @" + StringInterpolationProviderConfiguration.class.getName() + " instance";
                     }
                     if ( calledMethod.getName().equals("hashCode") && (args == null || args.length == 0) ) {
                         return this.hashCode();
@@ -68,15 +68,15 @@ public class PlaceholderProviderImpl
                 throw new InternalError("unexpected method dispatched: " + calledMethod);
             }
         };
-        DEFAULT_CONFIG = (PlaceholderProviderConfiguration) Proxy.newProxyInstance(
-            PlaceholderProviderConfiguration.class.getClassLoader(),
-            new Class[] { PlaceholderProviderConfiguration.class },
+        DEFAULT_CONFIG = (StringInterpolationProviderConfiguration) Proxy.newProxyInstance(
+            StringInterpolationProviderConfiguration.class.getClassLoader(),
+            new Class[] { StringInterpolationProviderConfiguration.class },
             handler
         );
     }
 
     private BundleContext bundleContext;
-    private PlaceholderProviderConfiguration config = DEFAULT_CONFIG;
+    private StringInterpolationProviderConfiguration config = DEFAULT_CONFIG;
     private Map<String, String> placeholderEntries = new HashMap<>();
 
     // ---------- SCR Integration ---------------------------------------------
@@ -85,7 +85,7 @@ public class PlaceholderProviderImpl
      * Activates this component (called by SCR before)
      */
     @Activate
-    protected void activate(final BundleContext bundleContext, final PlaceholderProviderConfiguration config) {
+    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()) {
@@ -110,7 +110,7 @@ public class PlaceholderProviderImpl
      * Modifies this component (called by SCR to update this component)
      */
     @Modified
-    protected void modified(final BundleContext bundleContext, final PlaceholderProviderConfiguration config) {
+    protected void modified(final BundleContext bundleContext, final StringInterpolationProviderConfiguration config) {
         this.deactivate();
         this.activate(bundleContext, config);
     }
@@ -169,7 +169,7 @@ public class PlaceholderProviderImpl
                 answer += value;
                 carret = carret + end - start + PLACEHOLDER_END_TOKEN.length();
             }
-            if(carret < line.length() - 1) {
+            if(carret < line.length()) {
                 // There is some text left after the last placeholder so copy this to the target line
                 answer += line.substring(carret);
             }
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 86f3766..682dacf 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sling.resourceresolver.impl.mapping;
 
+import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -32,10 +33,13 @@ import static org.mockito.Mockito.when;
 import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -59,6 +63,7 @@ import java.util.concurrent.atomic.AtomicLong;
 import org.apache.sling.api.SlingException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.api.resource.observation.ResourceChange;
@@ -102,7 +107,7 @@ public class MapEntriesTest {
     private EventAdmin eventAdmin;
 
     @Mock
-    private PlaceholderProvider placeholderProvider;
+    private StringInterpolationProvider stringInterpolationProvider;
 
     private Map<String, Map<String, String>> aliasMap;
 
@@ -140,7 +145,7 @@ public class MapEntriesTest {
         when(resourceResolver.findResources(anyString(), eq("sql"))).thenReturn(
                 Collections.<Resource> emptySet().iterator());
 
-        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, placeholderProvider);
+        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
         final Field aliasMapField = MapEntries.class.getDeclaredField("aliasMap");
         aliasMapField.setAccessible(true);
 
@@ -856,7 +861,7 @@ public class MapEntriesTest {
         addResource.setAccessible(true);
 
         when(resourceResolverFactory.isOptimizeAliasResolutionEnabled()).thenReturn(false);
-        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, placeholderProvider);
+        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
 
         Resource parent = mock(Resource.class);
         when(parent.getPath()).thenReturn("/parent");
@@ -881,7 +886,7 @@ public class MapEntriesTest {
         addResource.setAccessible(true);
 
         when(resourceResolverFactory.isOptimizeAliasResolutionEnabled()).thenReturn(false);
-        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, placeholderProvider);
+        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
 
         Resource parent = mock(Resource.class);
         when(parent.getPath()).thenReturn("/parent");
@@ -906,7 +911,7 @@ public class MapEntriesTest {
         removeAlias.setAccessible(true);
 
         when(resourceResolverFactory.isOptimizeAliasResolutionEnabled()).thenReturn(false);
-        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, placeholderProvider);
+        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
 
         Resource parent = mock(Resource.class);
         when(parent.getPath()).thenReturn("/parent");
@@ -1659,7 +1664,7 @@ public class MapEntriesTest {
 	final Method addResource = MapEntries.class.getDeclaredMethod("addResource", String.class, AtomicBoolean.class);
         addResource.setAccessible(true);
 
-        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, placeholderProvider);
+        mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
 
         Resource parent = mock(Resource.class);
         when(parent.getPath()).thenReturn("/parent");
@@ -1709,7 +1714,7 @@ public class MapEntriesTest {
 	final Method addResource = MapEntries.class.getDeclaredMethod("addResource", String.class, AtomicBoolean.class);
         addResource.setAccessible(true);
 
-        mapEntries = Mockito.spy(new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, placeholderProvider));
+        mapEntries = Mockito.spy(new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider));
         doReturn(100).when(mapEntries).getTraversalRetryInterval();
 
         Resource parent = mock(Resource.class);
@@ -1766,7 +1771,7 @@ public class MapEntriesTest {
 	final Method addResource = MapEntries.class.getDeclaredMethod("addResource", String.class, AtomicBoolean.class);
         addResource.setAccessible(true);
 
-        mapEntries = Mockito.spy(new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, placeholderProvider));
+        mapEntries = Mockito.spy(new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider));
         doReturn(100).when(mapEntries).getTraversalRetryInterval();
 
         Resource parent = mock(Resource.class);
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/PlaceholderProviderImplTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/PlaceholderProviderImplTest.java
deleted file mode 100644
index 472bcad..0000000
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/PlaceholderProviderImplTest.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.resourceresolver.impl.mapping;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.osgi.framework.BundleContext;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.when;
-
-public class PlaceholderProviderImplTest {
-
-    @Mock
-    private BundleContext bundleContext;
-
-    @Mock
-    private PlaceholderProviderConfiguration placeholderProviderConfiguration;
-
-    @SuppressWarnings({ "unchecked" })
-    @Before
-    public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void test_simple_one_placeholder() {
-        when(placeholderProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
-            new String[] { "one=two"}
-        );
-
-        PlaceholderProviderImpl placeholderProvider = new PlaceholderProviderImpl();
-        placeholderProvider.activate(bundleContext, placeholderProviderConfiguration);
-
-        String line = "{{one}}";
-        PlaceholderProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", PlaceholderProvider.STATUS.found, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "two", resolved);
-    }
-
-    @Test
-    public void test_simple_text_one_placeholder() {
-        when(placeholderProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
-            new String[] { "one=two"}
-        );
-
-        PlaceholderProviderImpl placeholderProvider = new PlaceholderProviderImpl();
-        placeholderProvider.activate(bundleContext, placeholderProviderConfiguration);
-
-        String line = "Here is {{one}}, too";
-        PlaceholderProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", PlaceholderProvider.STATUS.found, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "Here is two, too", resolved);
-    }
-
-    @Test
-    public void test_two_placeholders() {
-        when(placeholderProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
-            new String[] { "one=two", "three=four"}
-        );
-
-        PlaceholderProviderImpl placeholderProvider = new PlaceholderProviderImpl();
-        placeholderProvider.activate(bundleContext, placeholderProviderConfiguration);
-
-        String line = "{{one}} with another {{three}}";
-        PlaceholderProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", PlaceholderProvider.STATUS.found, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "two with another four", resolved);
-    }
-
-    @Test
-    public void test_three_placeholders() {
-        when(placeholderProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
-            new String[] { "one=two", "three=four", "five=six"}
-        );
-
-        PlaceholderProviderImpl placeholderProvider = new PlaceholderProviderImpl();
-        placeholderProvider.activate(bundleContext, placeholderProviderConfiguration);
-
-        String line = "Here comes {{one}} with another {{three}} equals {{five}}, horray!";
-        PlaceholderProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", PlaceholderProvider.STATUS.found, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "Here comes two with another four equals six, horray!", resolved);
-    }
-
-    @Test
-    public void test_no_placeholders() {
-        when(placeholderProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
-            new String[] { "one=two", "three=four", "five=six"}
-        );
-
-        PlaceholderProviderImpl placeholderProvider = new PlaceholderProviderImpl();
-        placeholderProvider.activate(bundleContext, placeholderProviderConfiguration);
-
-        String line = "Here comes is a text with no placeholders!";
-        PlaceholderProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", PlaceholderProvider.STATUS.none, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "Here comes is a text with no placeholders!", resolved);
-    }
-
-    @Test
-    public void test_unkown_placeholders() {
-        when(placeholderProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
-            new String[] { "one=two", "three=four", "five=six"}
-        );
-
-        PlaceholderProviderImpl placeholderProvider = new PlaceholderProviderImpl();
-        placeholderProvider.activate(bundleContext, placeholderProviderConfiguration);
-
-        String line = "Here comes {{unkown}} placeholders!";
-        PlaceholderProvider.Check check = placeholderProvider.hasPlaceholder(line);
-        assertEquals("Wrong Check status", PlaceholderProvider.STATUS.unknown, check.getStatus());
-        String resolved = placeholderProvider.resolve(check);
-        assertEquals("Wrong resolved line", "Here comes {{unkown}} placeholders!", resolved);
-    }
-}
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
new file mode 100644
index 0000000..8392637
--- /dev/null
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.resourceresolver.impl.mapping;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.resource.path.Path;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.event.EventAdmin;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+/**
+ * These are tests that are testing the Sling Interpolation Feature (SLING-7768)
+ * on the MapEntries level
+ */
+public class StringInterpolationMapEntriesTest {
+    private static final String PROP_REG_EXP = "sling:match";
+
+    @Mock
+    private MapConfigurationProvider resourceResolverFactory;
+
+    @Mock
+    private BundleContext bundleContext;
+
+    @Mock
+    private EventAdmin eventAdmin;
+
+    @Mock
+    private ResourceResolver resourceResolver;
+
+    @Mock
+    private StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration;
+
+    File vanityBloomFilterFile;
+
+    private Resource map;
+    private Resource http;
+
+    @SuppressWarnings({"unchecked"})
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(resourceResolverFactory.getServiceResourceResolver(any(Map.class))).thenReturn(resourceResolver);
+        when(resourceResolverFactory.isVanityPathEnabled()).thenReturn(true);
+        final List<MapConfigurationProvider.VanityPathConfig> configs = new ArrayList<>();
+        when(resourceResolverFactory.getVanityPathConfig()).thenReturn(configs);
+        when(resourceResolverFactory.isOptimizeAliasResolutionEnabled()).thenReturn(true);
+        when(resourceResolverFactory.isForceNoAliasTraversal()).thenReturn(true);
+        when(resourceResolverFactory.getObservationPaths()).thenReturn(new Path[] {new Path("/")});
+        when(resourceResolverFactory.getMapRoot()).thenReturn(MapEntries.DEFAULT_MAP_ROOT);
+        when(resourceResolverFactory.getMaxCachedVanityPathEntries()).thenReturn(-1L);
+        when(resourceResolverFactory.isMaxCachedVanityPathEntriesStartup()).thenReturn(true);
+        when(resourceResolver.findResources(anyString(), eq("sql"))).thenReturn(
+            Collections.<Resource> emptySet().iterator());
+        vanityBloomFilterFile = new File("target/test-classes/resourcesvanityBloomFilter.txt");
+        when(bundleContext.getDataFile("vanityBloomFilter.txt")).thenReturn(vanityBloomFilterFile);
+
+        map = setupEtcMapResource("/etc", "map");
+        http = setupEtcMapResource("http", map);
+    }
+
+    @Test
+    public void simple_node_string_interpolation() throws Exception {
+        // To avoid side effects the String Interpolation uses its own Resource Resolver
+        Resource sivOne = setupEtcMapResource("${siv.one}", http,PROP_REDIRECT_EXTERNAL, "/content/test-me");
+        StringInterpolationProvider stringInterpolationProvider = setupStringInterpolationProvider(new String[] {"siv.one=test-value"});
+
+        MapEntries mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
+        List<MapEntry> mapMaps = mapEntries.getResolveMaps();
+        assertEquals("Expected one mapping", 1, mapMaps.size());
+        MapEntry mapEntry = mapMaps.get(0);
+        assertEquals("Wrong String Interpolation for siv.one", "^http/test-value/", mapEntry.getPattern());
+        String[] redirects = mapEntry.getRedirect();
+        assertEquals("Expected one redirect", 1, redirects.length);
+        assertEquals("Wrong Mapping found for siv.one", "/content/test-me/", redirects[0]);
+    }
+
+    @Test
+    public void simple_match_string_interpolation() throws Exception {
+        // To avoid side effects the String Interpolation uses its own Resource Resolver
+        Resource sivOne = setupEtcMapResource("test-node", http,
+            PROP_REG_EXP, "${siv.one}/",
+            PROP_REDIRECT_EXTERNAL, "/content/test-me/"
+        );
+        StringInterpolationProvider stringInterpolationProvider = setupStringInterpolationProvider(new String[] {"siv.one=test-value"});
+
+        MapEntries mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider);
+        List<MapEntry> mapMaps = mapEntries.getResolveMaps();
+        assertEquals("Expected one mapping", 1, mapMaps.size());
+        MapEntry mapEntry = mapMaps.get(0);
+        assertEquals("Wrong String Interpolation for siv.one", "^http/test-value/", mapEntry.getPattern());
+        String[] redirects = mapEntry.getRedirect();
+        assertEquals("Expected one redirect", 1, redirects.length);
+        assertEquals("Wrong Mapping found for siv.one", "/content/test-me/", redirects[0]);
+    }
+
+    // -------------------------- private methods ----------
+
+    private ValueMap buildValueMap(Object... string) {
+        final Map<String, Object> data = new HashMap<>();
+        for (int i = 0; i < string.length; i = i + 2) {
+            data.put((String) string[i], string[i+1]);
+        }
+        return new ValueMapDecorator(data);
+    }
+
+    private Resource setupEtcMapResource(String parentPath, String name, String...valueMapPairs) {
+        return setupEtcMapResource0(parentPath, name, null, valueMapPairs);
+    }
+    private Resource setupEtcMapResource(String name, Resource parent, String...valueMapPairs) {
+        return setupEtcMapResource0(null, name, parent, valueMapPairs);
+    }
+    private Resource setupEtcMapResource0(String parentPath, String name, Resource parent, String...valueMapPairs) {
+        Resource resource = mock(Resource.class, withSettings().name(name).extraInterfaces(ResourceDecorator.class));
+        String path = (parent == null ? parentPath : parent.getPath()) + "/" + name;
+        when(resource.getPath()).thenReturn(path);
+        when(resource.getName()).thenReturn(name);
+        ValueMap valueMap = buildValueMap(valueMapPairs);
+        when(resource.getValueMap()).thenReturn(valueMap);
+        when(resource.adaptTo(ValueMap.class)).thenReturn(valueMap);
+        when(resourceResolver.getResource(resource.getPath())).thenReturn(resource);
+        if(parent != null) {
+            List<Resource> childList = ((ResourceDecorator) parent).getChildrenList();
+            childList.add(resource);
+        }
+        final List<Resource> childrenList = new ArrayList<>();
+        when(((ResourceDecorator) resource).getChildrenList()).thenReturn(childrenList);
+        // Delay the children list iterator to make sure all children are added beforehand
+        // Iterators have a modCount that is set when created. Any changes to the underlying list will
+        // change that modCount and the usage of the iterator will fail due to Concurrent Modification Exception
+        when(resource.listChildren()).thenAnswer(new Answer<Iterator<Resource>>() {
+            @Override
+            public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable {
+                return childrenList.iterator();
+            }
+        });
+
+        return resource;
+    }
+
+    private StringInterpolationProvider setupStringInterpolationProvider(final String[] placeholderValues) {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(placeholderValues);
+        StringInterpolationProviderImpl stringInterpolationProvider = new StringInterpolationProviderImpl();
+        stringInterpolationProvider.activate(bundleContext, stringInterpolationProviderConfiguration);
+        return stringInterpolationProvider;
+    }
+
+    /**
+     * Iterator to piggyback the list of Resources onto a Resource Mock
+     * so that we can add children to them and create the iterators after
+     * everything is setup
+     */
+    private static interface ResourceDecorator {
+        public List<Resource> getChildrenList();
+    }
+}
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
new file mode 100644
index 0000000..212c928
--- /dev/null
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationProviderImplTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.resourceresolver.impl.mapping;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.osgi.framework.BundleContext;
+
+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 {
+
+    @Mock
+    private BundleContext bundleContext;
+
+    @Mock
+    private StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration;
+
+    @SuppressWarnings({ "unchecked" })
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void test_simple_one_placeholder() {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
+            new String[] { "one=two"}
+        );
+
+        StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
+        placeholderProvider.activate(bundleContext, 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);
+    }
+
+    @Test
+    public void test_simple_text_one_placeholder() {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
+            new String[] { "one=two"}
+        );
+        StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
+        placeholderProvider.activate(bundleContext, 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);
+    }
+
+    @Test
+    public void test_two_placeholders() {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
+            new String[] { "one=two", "three=four"}
+        );
+        StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
+        placeholderProvider.activate(bundleContext, 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);
+    }
+
+    @Test
+    public void test_three_placeholders() {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
+            new String[] { "one=two", "three=four", "five=six"}
+        );
+
+        StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
+        placeholderProvider.activate(bundleContext, 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);
+    }
+
+    @Test
+    public void test_no_placeholders() {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
+            new String[] { "one=two", "three=four", "five=six"}
+        );
+
+        StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
+        placeholderProvider.activate(bundleContext, 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);
+    }
+
+    @Test
+    public void test_unkown_placeholders() {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
+            new String[] { "one=two", "three=four", "five=six"}
+        );
+
+        StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
+        placeholderProvider.activate(bundleContext, 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);
+    }
+
+    @Test
+    public void test_trailing_slash_placeholders() {
+        when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(
+            new String[] { "siv.one=test-value"}
+        );
+
+        StringInterpolationProviderImpl placeholderProvider = new StringInterpolationProviderImpl();
+        placeholderProvider.activate(bundleContext, 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);
+    }
+}