You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ma...@apache.org on 2022/06/15 01:14:50 UTC

[commons-configuration] branch master updated: Make default interpolation prefix lookups configurable via system property. Remove dns, url, and script lookups from defaults.

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

mattjuntunen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-configuration.git


The following commit(s) were added to refs/heads/master by this push:
     new f025bc39 Make default interpolation prefix lookups configurable via system property. Remove dns, url, and script lookups from defaults.
f025bc39 is described below

commit f025bc399e8125ffc7701ac74f09b833c5b5e152
Author: Matt Juntunen <ma...@apache.org>
AuthorDate: Wed Jun 8 23:08:26 2022 -0400

    Make default interpolation prefix lookups configurable via system property. Remove dns, url, and script lookups from defaults.
---
 src/changes/changes.xml                            |   5 +
 .../configuration2/AbstractConfiguration.java      |   4 +
 .../interpol/ConfigurationInterpolator.java        | 225 +++++++++++++++++++--
 .../configuration2/interpol/DefaultLookups.java    |  13 +-
 .../interpol/TestConfigurationInterpolator.java    | 160 ++++++++++++++-
 5 files changed, 378 insertions(+), 29 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 51b3ee68..de979283 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -98,6 +98,11 @@
          Add ImmutableConfiguration.getDuration() methods.
        </action>       
        <!-- UPDATES -->
+       <action type="update" dev="mattjuntunen">
+        Make default interpolation prefix lookups configurable via system property. Remove dns, url, and script 
+        lookups from defaults. If these lookups are required for use in AbstractConfiguration subclasses, they must 
+        be enabled via system property. See ConfigurationInterpolator.getDefaultPrefixLookups() for details.
+       </action>
        <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">
          Bump actions/cache from 2 to 3.0.4 #99, #151, #169.
        </action>
diff --git a/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java b/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java
index 3cde89c7..c1b0f0dd 100644
--- a/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java
@@ -211,9 +211,13 @@ public abstract class AbstractConfiguration extends BaseEventSource implements C
 
     /**
      * Returns the {@code ConfigurationInterpolator} object that manages the lookup objects for resolving variables.
+     * Unless a custom interpolator has been set or the instance has been modified, the returned interpolator will
+     * resolve values from this configuration instance and support the
+     * {@link ConfigurationInterpolator#getDefaultPrefixLookups() default prefix lookups}.
      *
      * @return the {@code ConfigurationInterpolator} associated with this configuration
      * @since 1.4
+     * @see ConfigurationInterpolator#getDefaultPrefixLookups()
      */
     @Override
     public ConfigurationInterpolator getInterpolator() {
diff --git a/src/main/java/org/apache/commons/configuration2/interpol/ConfigurationInterpolator.java b/src/main/java/org/apache/commons/configuration2/interpol/ConfigurationInterpolator.java
index 8c32ed04..c0b1a8ad 100644
--- a/src/main/java/org/apache/commons/configuration2/interpol/ConfigurationInterpolator.java
+++ b/src/main/java/org/apache/commons/configuration2/interpol/ConfigurationInterpolator.java
@@ -25,13 +25,13 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.function.Function;
 
 import org.apache.commons.text.StringSubstitutor;
-import org.apache.commons.text.lookup.DefaultStringLookup;
 
 /**
  * <p>
@@ -89,6 +89,17 @@ import org.apache.commons.text.lookup.DefaultStringLookup;
  * @since 1.4
  */
 public class ConfigurationInterpolator {
+
+    /**
+     * Name of the system property used to determine the lookups added by the
+     * {@link #getDefaultPrefixLookups()} method. Use of this property is only required
+     * in cases where the set of default lookups must be modified.
+     *
+     * @since 2.8
+     */
+    public static final String DEFAULT_PREFIX_LOOKUPS_PROPERTY =
+            "org.apache.commons.configuration2.interpol.ConfigurationInterpolator.defaultPrefixLookups";
+
     /** Constant for the prefix separator. */
     private static final char PREFIX_SEPARATOR = ':';
 
@@ -104,23 +115,6 @@ public class ConfigurationInterpolator {
     /** The length of {@link #VAR_END}. */
     private static final int VAR_END_LENGTH = VAR_END.length();
 
-    /** A map containing the default prefix lookups. */
-    private static final Map<String, Lookup> DEFAULT_PREFIX_LOOKUPS;
-
-    static {
-        // TODO Perhaps a 3.0 version should only use Commons Text lookups.
-        // Add our own lookups.
-        final Map<String, Lookup> lookups = new HashMap<>();
-        for (final DefaultLookups lookup : DefaultLookups.values()) {
-            lookups.put(lookup.getPrefix(), lookup.getLookup());
-        }
-        // Add Apache Commons Text lookups but don't override existing keys.
-        for (final DefaultStringLookup lookup : DefaultStringLookup.values()) {
-            lookups.putIfAbsent(lookup.getKey(), new StringLookupAdapter(lookup.getStringLookup()));
-        }
-        DEFAULT_PREFIX_LOOKUPS = Collections.unmodifiableMap(lookups);
-    }
-
     /** A map with the currently registered lookup objects. */
     private final Map<String, Lookup> prefixLookups;
 
@@ -189,14 +183,111 @@ public class ConfigurationInterpolator {
 
     /**
      * Returns a map containing the default prefix lookups. Every configuration object derived from
-     * {@code AbstractConfiguration} is by default initialized with a {@code ConfigurationInterpolator} containing these
-     * {@code Lookup} objects and their prefixes. The map cannot be modified
+     * {@code AbstractConfiguration} is by default initialized with a {@code ConfigurationInterpolator} containing
+     * these {@code Lookup} objects and their prefixes. The map cannot be modified.
+     *
+     * <p>
+     * All of the lookups present in the returned map are from {@link DefaultLookups}. However, not all of the
+     * available lookups are included by default. Specifically, lookups that can execute code (e.g.,
+     * {@link DefaultLookups#SCRIPT SCRIPT}) and those that can result in contact with remote servers (e.g.,
+     * {@link DefaultLookups#URL URL} and {@link DefaultLookups#DNS DNS}) are not included. If this behavior
+     * must be modified, users can define the {@value #DEFAULT_PREFIX_LOOKUPS_PROPERTY} system property
+     * with a comma-separated list of {@link DefaultLookups} enum names to be included in the set of defaults.
+     * For example, setting this system property to {@code "BASE64_ENCODER,ENVIRONMENT"} will only include the
+     * {@link DefaultLookups#BASE64_ENCODER BASE64_ENCODER} and
+     * {@link DefaultLookups#ENVIRONMENT ENVIRONMENT} lookups. Setting the property to the empty string will
+     * cause no defaults to be configured.
+     * </p>
+     *
+     * <p><strong>Default Lookups</strong></p>
+     * <table>
+     * <tr>
+     *  <th>Prefix</th>
+     *  <th>Lookup</th>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_BASE64_DECODER}</td>
+     *  <td>{@link DefaultLookups#BASE64_DECODER BASE64_DECODER}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_BASE64_ENCODER}</td>
+     *  <td>{@link DefaultLookups#BASE64_ENCODER BASE64_ENCODER}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_CONST}</td>
+     *  <td>{@link DefaultLookups#CONST CONST}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_DATE}</td>
+     *  <td>{@link DefaultLookups#DATE DATE}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_ENV}</td>
+     *  <td>{@link DefaultLookups#ENVIRONMENT ENVIRONMENT}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_FILE}</td>
+     *  <td>{@link DefaultLookups#FILE FILE}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_JAVA}</td>
+     *  <td>{@link DefaultLookups#JAVA JAVA}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_LOCALHOST}</td>
+     *  <td>{@link DefaultLookups#LOCAL_HOST LOCAL_HOST}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_PROPERTIES}</td>
+     *  <td>{@link DefaultLookups#PROPERTIES PROPERTIES}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_RESOURCE_BUNDLE}</td>
+     *  <td>{@link DefaultLookups#RESOURCE_BUNDLE RESOURCE_BUNDLE}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_SYS}</td>
+     *  <td>{@link DefaultLookups#SYSTEM_PROPERTIES SYSTEM_PROPERTIES}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_URL_DECODER}</td>
+     *  <td>{@link DefaultLookups#URL_DECODER URL_DECODER}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_URL_ENCODER}</td>
+     *  <td>{@link DefaultLookups#URL_ENCODER URL_ENCODER}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_XML}</td>
+     *  <td>{@link DefaultLookups#XML XML}</td>
+     * </tr>
+     * </table>
+     *
+     * <p><strong>Additional Lookups (not included by default)</strong></p>
+     * <table>
+     * <tr>
+     *  <th>Prefix</th>
+     *  <th>Lookup</th>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_DNS}</td>
+     *  <td>{@link DefaultLookups#DNS DNS}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_URL}</td>
+     *  <td>{@link DefaultLookups#URL URL}</td>
+     * </tr>
+     * <tr>
+     *  <td>{@value org.apache.commons.text.lookup.StringLookupFactory#KEY_SCRIPT}</td>
+     *  <td>{@link DefaultLookups#SCRIPT SCRIPT}</td>
+     * </tr>
+     * </table>
      *
      * @return a map with the default prefix {@code Lookup} objects and their prefixes
      * @since 2.0
      */
     public static Map<String, Lookup> getDefaultPrefixLookups() {
-        return DEFAULT_PREFIX_LOOKUPS;
+        return DefaultPrefixLookupsHolder.INSTANCE.getDefaultPrefixLookups();
     }
 
     /**
@@ -513,6 +604,98 @@ public class ConfigurationInterpolator {
         this.parentInterpolator = parentInterpolator;
     }
 
+    /**
+     * Internal class used to construct the default {@link Lookup} map used by
+     * {@link ConfigurationInterpolator#getDefaultPrefixLookups()}.
+     */
+    static final class DefaultPrefixLookupsHolder {
+
+        /** Singleton instance, initialized with the system properties. */
+        static final DefaultPrefixLookupsHolder INSTANCE = new DefaultPrefixLookupsHolder(System.getProperties());
+
+        /** Default lookup map. */
+        private final Map<String, Lookup> defaultLookups;
+
+        /**
+         * Construct a new instance initialized with the given properties.
+         * @param props initialization properties
+         */
+        DefaultPrefixLookupsHolder(final Properties props) {
+            final Map<String, Lookup> lookups =
+                    props.containsKey(ConfigurationInterpolator.DEFAULT_PREFIX_LOOKUPS_PROPERTY)
+                        ? parseLookups(props.getProperty(ConfigurationInterpolator.DEFAULT_PREFIX_LOOKUPS_PROPERTY))
+                        : createDefaultLookups();
+
+            defaultLookups = Collections.unmodifiableMap(lookups);
+        }
+
+        /**
+         * Get the default prefix lookups map.
+         * @return default prefix lookups map
+         */
+        Map<String, Lookup> getDefaultPrefixLookups() {
+            return defaultLookups;
+        }
+
+        /**
+         * Create the lookup map used when the user has requested no customization.
+         * @return default lookup map
+         */
+        private static Map<String, Lookup> createDefaultLookups() {
+            final Map<String, Lookup> lookupMap = new HashMap<>();
+
+            addLookup(DefaultLookups.BASE64_DECODER, lookupMap);
+            addLookup(DefaultLookups.BASE64_ENCODER, lookupMap);
+            addLookup(DefaultLookups.CONST, lookupMap);
+            addLookup(DefaultLookups.DATE, lookupMap);
+            addLookup(DefaultLookups.ENVIRONMENT, lookupMap);
+            addLookup(DefaultLookups.FILE, lookupMap);
+            addLookup(DefaultLookups.JAVA, lookupMap);
+            addLookup(DefaultLookups.LOCAL_HOST, lookupMap);
+            addLookup(DefaultLookups.PROPERTIES, lookupMap);
+            addLookup(DefaultLookups.RESOURCE_BUNDLE, lookupMap);
+            addLookup(DefaultLookups.SYSTEM_PROPERTIES, lookupMap);
+            addLookup(DefaultLookups.URL_DECODER, lookupMap);
+            addLookup(DefaultLookups.URL_ENCODER, lookupMap);
+            addLookup(DefaultLookups.XML, lookupMap);
+
+            return lookupMap;
+        }
+
+        /**
+         * Construct a lookup map by parsing the given string. The string is expected to contain
+         * comma or space-separated names of values from the {@link DefaultLookups} enum.
+         * @param str string to parse; not null
+         * @return lookup map parsed from the given string
+         * @throws IllegalArgumentException if the string does not contain a valid default lookup
+         *      definition
+         */
+        private static Map<String, Lookup> parseLookups(final String str) {
+            final Map<String, Lookup> lookupMap = new HashMap<>();
+
+            try {
+                for (final String lookupName : str.split("[\\s,]+")) {
+                    if (!lookupName.isEmpty()) {
+                        addLookup(DefaultLookups.valueOf(lookupName.toUpperCase()), lookupMap);
+                    }
+                }
+            } catch (IllegalArgumentException exc) {
+                throw new IllegalArgumentException("Invalid default lookups definition: " + str, exc);
+            }
+
+            return lookupMap;
+        }
+
+        /**
+         * Add the prefix and lookup from {@code lookup} to {@code map}.
+         * @param lookup lookup to add
+         * @param map map to add to
+         */
+        private static void addLookup(final DefaultLookups lookup, final Map<String, Lookup> map) {
+            map.put(lookup.getPrefix(), lookup.getLookup());
+        }
+    }
+
     /** Class encapsulating the default logic to convert resolved variable values into strings.
      * This class is thread-safe.
      */
diff --git a/src/main/java/org/apache/commons/configuration2/interpol/DefaultLookups.java b/src/main/java/org/apache/commons/configuration2/interpol/DefaultLookups.java
index 00f73ff7..423e3df1 100644
--- a/src/main/java/org/apache/commons/configuration2/interpol/DefaultLookups.java
+++ b/src/main/java/org/apache/commons/configuration2/interpol/DefaultLookups.java
@@ -20,16 +20,17 @@ import org.apache.commons.text.lookup.StringLookupFactory;
 
 /**
  * <p>
- * An enumeration class defining constants for the {@code Lookup} objects available for each {@code Configuration}
- * object per default.
+ * An enumeration class defining constants for built-in {@code Lookup} objects available for
+ * {@code Configuration} instances.
  * </p>
  * <p>
- * When a new configuration object derived from {@code AbstractConfiguration} is created it installs a
- * {@link ConfigurationInterpolator} with a default set of {@link Lookup} objects. These lookups are defined by this
- * enumeration class.
+ * When a new configuration object derived from {@code AbstractConfiguration} is created, it installs a
+ * {@link ConfigurationInterpolator} containing a default set of {@link Lookup} objects. These lookups are
+ * defined by this enumeration class, however not all lookups may be included in the defaults. See
+ * {@link ConfigurationInterpolator#getDefaultPrefixLookups()} for details.
  * </p>
  * <p>
- * All the default {@code Lookup} classes are state-less, thus their instances can be shared between multiple
+ * All the {@code Lookup}s defined here are state-less, thus their instances can be shared between multiple
  * configuration objects. Therefore, it makes sense to keep shared instances in this enumeration class.
  * </p>
  *
diff --git a/src/test/java/org/apache/commons/configuration2/interpol/TestConfigurationInterpolator.java b/src/test/java/org/apache/commons/configuration2/interpol/TestConfigurationInterpolator.java
index 2a1bb903..c1b69f7b 100644
--- a/src/test/java/org/apache/commons/configuration2/interpol/TestConfigurationInterpolator.java
+++ b/src/test/java/org/apache/commons/configuration2/interpol/TestConfigurationInterpolator.java
@@ -23,17 +23,23 @@ import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
 import java.util.function.Function;
 
+import org.apache.commons.text.lookup.StringLookupFactory;
 import org.easymock.EasyMock;
 import org.junit.Before;
 import org.junit.Test;
@@ -206,11 +212,23 @@ public class TestConfigurationInterpolator {
      */
     @Test
     public void testGetDefaultPrefixLookups() {
+        final EnumSet<DefaultLookups> excluded = EnumSet.of(
+                DefaultLookups.DNS,
+                DefaultLookups.URL,
+                DefaultLookups.SCRIPT);
+
+        final EnumSet<DefaultLookups> included = EnumSet.complementOf(excluded);
+
         final Map<String, Lookup> lookups = ConfigurationInterpolator.getDefaultPrefixLookups();
-        assertEquals("Wrong number of lookups", DefaultLookups.values().length, lookups.size());
-        for (final DefaultLookups l : DefaultLookups.values()) {
+
+        assertEquals("Wrong number of lookups", included.size(), lookups.size());
+        for (final DefaultLookups l : included) {
             assertSame("Wrong entry for " + l, l.getLookup(), lookups.get(l.getPrefix()));
         }
+
+        for (final DefaultLookups l : excluded) {
+            assertNull("Unexpected entry for " + l, lookups.get(l.getPrefix()));
+        }
     }
 
     /**
@@ -684,4 +702,142 @@ public class TestConfigurationInterpolator {
         assertNull("Variable could be resolved", interpolator.resolve("UnknownPrefix:" + TEST_NAME));
         assertNull("Variable with empty prefix could be resolved", interpolator.resolve(":" + TEST_NAME));
     }
+
+    @Test
+    public void testDefaultStringLookupsHolder_lookupsPropertyNotPresent() {
+        checkDefaultPrefixLookupsHolder(new Properties(),
+                "base64",
+                StringLookupFactory.KEY_BASE64_DECODER,
+                StringLookupFactory.KEY_BASE64_ENCODER,
+                StringLookupFactory.KEY_CONST,
+                StringLookupFactory.KEY_DATE,
+                StringLookupFactory.KEY_ENV,
+                StringLookupFactory.KEY_FILE,
+                StringLookupFactory.KEY_JAVA,
+                StringLookupFactory.KEY_LOCALHOST,
+                StringLookupFactory.KEY_PROPERTIES,
+                StringLookupFactory.KEY_RESOURCE_BUNDLE,
+                StringLookupFactory.KEY_SYS,
+                StringLookupFactory.KEY_URL_DECODER,
+                StringLookupFactory.KEY_URL_ENCODER,
+                StringLookupFactory.KEY_XML);
+    }
+
+    @Test
+    public void testDefaultStringLookupsHolder_lookupsPropertyEmptyAndBlank() {
+        final Properties propsWithNull = new Properties();
+        propsWithNull.setProperty(ConfigurationInterpolator.DEFAULT_PREFIX_LOOKUPS_PROPERTY, "");
+
+        checkDefaultPrefixLookupsHolder(propsWithNull);
+
+        final Properties propsWithBlank = new Properties();
+        propsWithBlank.setProperty(ConfigurationInterpolator.DEFAULT_PREFIX_LOOKUPS_PROPERTY, " ");
+
+        checkDefaultPrefixLookupsHolder(propsWithBlank);
+    }
+
+    @Test
+    public void testDefaultStringLookupsHolder_givenSingleLookup() {
+        final Properties props = new Properties();
+        props.setProperty(ConfigurationInterpolator.DEFAULT_PREFIX_LOOKUPS_PROPERTY, "base64_encoder");
+
+        checkDefaultPrefixLookupsHolder(props,
+                "base64",
+                StringLookupFactory.KEY_BASE64_ENCODER);
+    }
+
+    @Test
+    public void testDefaultStringLookupsHolder_givenSingleLookup_weirdString() {
+        final Properties props = new Properties();
+        props.setProperty(ConfigurationInterpolator.DEFAULT_PREFIX_LOOKUPS_PROPERTY, " \n \t  ,, DnS , , ");
+
+        checkDefaultPrefixLookupsHolder(props, StringLookupFactory.KEY_DNS);
+    }
+
+    @Test
+    public void testDefaultStringLookupsHolder_multipleLookups() {
+        final Properties props = new Properties();
+        props.setProperty(ConfigurationInterpolator.DEFAULT_PREFIX_LOOKUPS_PROPERTY, "dns, url script ");
+
+        checkDefaultPrefixLookupsHolder(props,
+                StringLookupFactory.KEY_DNS,
+                StringLookupFactory.KEY_URL,
+                StringLookupFactory.KEY_SCRIPT);
+    }
+
+    @Test
+    public void testDefaultStringLookupsHolder_allLookups() {
+        final Properties props = new Properties();
+        props.setProperty(ConfigurationInterpolator.DEFAULT_PREFIX_LOOKUPS_PROPERTY,
+                "BASE64_DECODER BASE64_ENCODER const, date, dns, environment "
+                + "file ,java, local_host properties, resource_bundle,script,system_properties "
+                + "url url_decoder  , url_encoder, xml");
+
+        checkDefaultPrefixLookupsHolder(props,
+                "base64",
+                StringLookupFactory.KEY_BASE64_DECODER,
+                StringLookupFactory.KEY_BASE64_ENCODER,
+                StringLookupFactory.KEY_CONST,
+                StringLookupFactory.KEY_DATE,
+                StringLookupFactory.KEY_ENV,
+                StringLookupFactory.KEY_FILE,
+                StringLookupFactory.KEY_JAVA,
+                StringLookupFactory.KEY_LOCALHOST,
+                StringLookupFactory.KEY_PROPERTIES,
+                StringLookupFactory.KEY_RESOURCE_BUNDLE,
+                StringLookupFactory.KEY_SYS,
+                StringLookupFactory.KEY_URL_DECODER,
+                StringLookupFactory.KEY_URL_ENCODER,
+                StringLookupFactory.KEY_XML,
+
+                StringLookupFactory.KEY_DNS,
+                StringLookupFactory.KEY_URL,
+                StringLookupFactory.KEY_SCRIPT);
+    }
+
+    @Test
+    public void testDefaultStringLookupsHolder_invalidLookupsDefinition() {
+        final Properties props = new Properties();
+        props.setProperty(ConfigurationInterpolator.DEFAULT_PREFIX_LOOKUPS_PROPERTY, "base64_encoder nope");
+
+        try {
+            new ConfigurationInterpolator.DefaultPrefixLookupsHolder(props);
+
+            fail("Operation should have failed");
+        } catch (Exception exc) {
+            assertEquals("Invalid default lookups definition: base64_encoder nope", exc.getMessage());
+        }
+    }
+
+    private static void checkDefaultPrefixLookupsHolder(final Properties props, final String... keys) {
+        final ConfigurationInterpolator.DefaultPrefixLookupsHolder holder =
+                new ConfigurationInterpolator.DefaultPrefixLookupsHolder(props);
+
+        final Map<String, Lookup> lookupMap = holder.getDefaultPrefixLookups();
+
+        assertMappedLookups(lookupMap, keys);
+    }
+
+    private static void assertMappedLookups(final Map<String, Lookup> lookupMap, final String... keys) {
+        final Set<String> remainingKeys = new HashSet<>(lookupMap.keySet());
+
+        for (final String key : keys) {
+            assertNotNull("Expected map to contain string lookup for key " + key, key);
+
+            remainingKeys.remove(key);
+        }
+
+        assertTrue("Unexpected keys in lookup map: " + remainingKeys, remainingKeys.isEmpty());
+    }
+
+    /**
+     * Main method used to verify the default lookups resolved during JVM execution.
+     * @param args
+     */
+    public static void main(final String[] args) {
+        System.out.println("Default lookups");
+        for (final String key : ConfigurationInterpolator.getDefaultPrefixLookups().keySet()) {
+            System.out.println("- " + key);
+        }
+    }
 }