[FELIX-2663] Allow ConfigInstaller to perform placeholder substitution from framework properties

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1027382 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/ConfigInstaller.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/ConfigInstaller.java
index e386d6f..31e63d8 100644
--- a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/ConfigInstaller.java
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/ConfigInstaller.java
@@ -175,7 +175,7 @@
                 } else {
                     p.load(in);
                 }
-                InterpolationHelper.performSubstitution((Map) p);
+                InterpolationHelper.performSubstitution((Map) p, context);
                 ht.putAll(p);
             }
             else if ( f.getName().endsWith( ".config" ) )
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/FileInstall.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/FileInstall.java
index aa9818d..c17628b 100644
--- a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/FileInstall.java
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/FileInstall.java
@@ -187,7 +187,7 @@
 
     public void updated(String pid, Dictionary properties)
     {
-        InterpolationHelper.performSubstitution(new DictionaryAsMap(properties));
+        InterpolationHelper.performSubstitution(new DictionaryAsMap(properties), context);
         DirectoryWatcher watcher = null;
         synchronized (watchers)
         {
diff --git a/utils/src/main/java/org/apache/felix/utils/properties/InterpolationHelper.java b/utils/src/main/java/org/apache/felix/utils/properties/InterpolationHelper.java
index 36d3c7f..c9c210f 100644
--- a/utils/src/main/java/org/apache/felix/utils/properties/InterpolationHelper.java
+++ b/utils/src/main/java/org/apache/felix/utils/properties/InterpolationHelper.java
@@ -16,6 +16,8 @@
  */
 package org.apache.felix.utils.properties;
 
+import org.osgi.framework.BundleContext;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -55,10 +57,20 @@
      */
     public static void performSubstitution(Map<String,String> properties)
     {
+        performSubstitution(properties, null);
+    }
+
+    /**
+     * Perform substitution on a property set
+     *
+     * @param properties the property set to perform substitution on
+     */
+    public static void performSubstitution(Map<String,String> properties, BundleContext context)
+    {
         for (String name : properties.keySet())
         {
             String value = properties.get(name);
-            properties.put(name, substVars(value, name, null, properties));
+            properties.put(name, substVars(value, name, null, properties, context));
         }
     }
 
@@ -79,11 +91,16 @@
      *        detect cycles.
      * @param cycleMap Map of variable references used to detect nested cycles.
      * @param configProps Set of configuration properties.
+     * @param context the bundle context to retrieve properties from
      * @return The value of the specified string after system property substitution.
      * @throws IllegalArgumentException If there was a syntax error in the
      *         property placeholder syntax or a recursive variable reference.
      **/
-    public static String substVars(String val, String currentKey, Map<String,String> cycleMap, Map<String,String> configProps)
+    public static String substVars(String val,
+                                   String currentKey,
+                                   Map<String,String> cycleMap,
+                                   Map<String,String> configProps,
+                                   BundleContext context)
         throws IllegalArgumentException
     {
         if (cycleMap == null)
@@ -147,8 +164,22 @@
         String substValue = (String) ((configProps != null) ? configProps.get(variable) : null);
         if (substValue == null)
         {
-            // Ignore unknown property values.
-            substValue = variable.length() > 0 ? System.getProperty(variable, "") : "";
+            if (variable.length() <= 0)
+            {
+                substValue = "";
+            }
+            else if (context != null)
+            {
+                substValue = context.getProperty(variable);
+                if (substValue == null)
+                {
+                    substValue = "";
+                }
+            }
+            else
+            {
+                substValue = System.getProperty(variable, "");
+            }
         }
 
         // Remove the found variable from the cycle map, since
@@ -163,7 +194,7 @@
 
         // Now perform substitution again, since there could still
         // be substitutions to make.
-        val = substVars(val, currentKey, cycleMap, configProps);
+        val = substVars(val, currentKey, cycleMap, configProps, context);
 
         // Remove escape characters preceding {, } and \
         val = unescape(val);
diff --git a/utils/src/test/java/org/apache/felix/utils/properties/InterpolationHelperTest.java b/utils/src/test/java/org/apache/felix/utils/properties/InterpolationHelperTest.java
index 5e4dcc4..f4ea8ea 100644
--- a/utils/src/test/java/org/apache/felix/utils/properties/InterpolationHelperTest.java
+++ b/utils/src/test/java/org/apache/felix/utils/properties/InterpolationHelperTest.java
@@ -23,6 +23,12 @@
 
 public class InterpolationHelperTest extends TestCase {
 
+    private MockBundleContext context;
+
+    protected void setUp() throws Exception {
+        context = new MockBundleContext();
+    }
+
     public void testBasicSubstitution()
     {
         System.setProperty("value1", "sub_value1");
@@ -34,7 +40,7 @@
         for (Enumeration e = props.keys(); e.hasMoreElements();)
         {
             String name = (String) e.nextElement();
-            props.put(name, InterpolationHelper.substVars((String) props.get(name), name, null, props));
+            props.put(name, InterpolationHelper.substVars((String) props.get(name), name, null, props, context));
         }
 
         assertEquals("value0", props.get("key0"));
@@ -43,26 +49,53 @@
 
     }
 
+    public void testBasicSubstitutionWithContext()
+    {
+        System.setProperty("value1", "sub_value1");
+        System.setProperty("value2", "sub_value2");
+        context.setProperty("value3", "context_value1");
+        context.setProperty("value2", "context_value2");
+
+        Hashtable props = new Hashtable();
+        props.put("key0", "value0");
+        props.put("key1", "${value1}");
+        props.put("key2", "${value2}");
+        props.put("key3", "${value3}");
+
+        for (Enumeration e = props.keys(); e.hasMoreElements();)
+        {
+            String name = (String) e.nextElement();
+            props.put(name,
+                    InterpolationHelper.substVars((String) props.get(name), name, null, props, context));
+        }
+
+        assertEquals("value0", props.get("key0"));
+        assertEquals("sub_value1", props.get("key1"));
+        assertEquals("context_value2", props.get("key2"));
+        assertEquals("context_value1", props.get("key3"));
+
+    }
+
     public void testSubstitutionFailures()
     {
-        assertEquals("a}", InterpolationHelper.substVars("a}", "b", null, new Hashtable()));
-        assertEquals("${a", InterpolationHelper.substVars("${a", "b", null, new Hashtable()));
+        assertEquals("a}", InterpolationHelper.substVars("a}", "b", null, new Hashtable(), context));
+        assertEquals("${a", InterpolationHelper.substVars("${a", "b", null, new Hashtable(), context));
     }
 
     public void testEmptyVariable() {
-        assertEquals("", InterpolationHelper.substVars("${}", "b", null, new Hashtable()));
+        assertEquals("", InterpolationHelper.substVars("${}", "b", null, new Hashtable(), context));
     }
 
     public void testInnerSubst() {
         Hashtable props = new Hashtable();
         props.put("a", "b");
         props.put("b", "c");
-        assertEquals("c", InterpolationHelper.substVars("${${a}}", "z", null, props));
+        assertEquals("c", InterpolationHelper.substVars("${${a}}", "z", null, props, context));
     }
 
     public void testSubstLoop() {
         try {
-            InterpolationHelper.substVars("${a}", "a", null, new Hashtable());
+            InterpolationHelper.substVars("${a}", "a", null, new Hashtable(), context);
             fail("Should have thrown an exception");
         } catch (IllegalArgumentException e) {
             // expected
@@ -71,9 +104,9 @@
 
     public void testSubstitutionEscape()
     {
-        assertEquals("${a}", InterpolationHelper.substVars("$\\{a${#}\\}", "b", null, new Hashtable()));
-        assertEquals("${a}", InterpolationHelper.substVars("$\\{a\\}${#}", "b", null, new Hashtable()));
-        assertEquals("${a}", InterpolationHelper.substVars("$\\{a\\}", "b", null, new Hashtable()));
+        assertEquals("${a}", InterpolationHelper.substVars("$\\{a${#}\\}", "b", null, new Hashtable(), context));
+        assertEquals("${a}", InterpolationHelper.substVars("$\\{a\\}${#}", "b", null, new Hashtable(), context));
+        assertEquals("${a}", InterpolationHelper.substVars("$\\{a\\}", "b", null, new Hashtable(), context));
     }
 
 }
diff --git a/utils/src/test/java/org/apache/felix/utils/properties/MockBundleContext.java b/utils/src/test/java/org/apache/felix/utils/properties/MockBundleContext.java
new file mode 100644
index 0000000..7a69b5e
--- /dev/null
+++ b/utils/src/test/java/org/apache/felix/utils/properties/MockBundleContext.java
@@ -0,0 +1,134 @@
+/*
+ * 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.felix.utils.properties;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Properties;
+import java.util.Dictionary;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+public class MockBundleContext implements BundleContext {
+    private Properties properties = new Properties();
+
+    public void setProperty(String name, String value) {
+        this.properties.setProperty(name, value);
+    }
+    public String getProperty(String name) {
+        String value = this.properties.getProperty(name);
+        if (value == null) {
+            value = System.getProperty(name);
+        }
+        return value;
+    }
+
+    public Bundle getBundle() {
+        throw new UnsupportedOperationException();
+    }
+
+    public Bundle installBundle(String s) throws BundleException {
+        throw new UnsupportedOperationException();
+    }
+
+    public Bundle installBundle(String s, InputStream stream) throws BundleException {
+        throw new UnsupportedOperationException();
+    }
+
+    public Bundle getBundle(long l) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Bundle[] getBundles() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void addServiceListener(ServiceListener listener, String s) throws InvalidSyntaxException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void addServiceListener(ServiceListener listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void removeServiceListener(ServiceListener listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void addBundleListener(BundleListener listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void removeBundleListener(BundleListener listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void addFrameworkListener(FrameworkListener listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void removeFrameworkListener(FrameworkListener listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    public ServiceRegistration registerService(String[] strings, Object o, Dictionary dictionary) {
+        throw new UnsupportedOperationException();
+    }
+
+    public ServiceRegistration registerService(String s, Object o, Dictionary dictionary) {
+        throw new UnsupportedOperationException();
+    }
+
+    public ServiceReference[] getServiceReferences(String s, String s1) throws InvalidSyntaxException {
+        throw new UnsupportedOperationException();
+    }
+
+    public ServiceReference[] getAllServiceReferences(String s, String s1) throws InvalidSyntaxException {
+        throw new UnsupportedOperationException();
+    }
+
+    public ServiceReference getServiceReference(String s) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object getService(ServiceReference reference) {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean ungetService(ServiceReference reference) {
+        throw new UnsupportedOperationException();
+    }
+
+    public File getDataFile(String s) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Filter createFilter(String s) throws InvalidSyntaxException {
+        throw new UnsupportedOperationException();
+    }
+}