You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ro...@apache.org on 2022/04/07 21:56:06 UTC

[felix-dev] branch master updated: FELIX-6518 - Enable interpolation plugin to self reference properties

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

rotty3000 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new c233f6922d FELIX-6518 - Enable interpolation plugin to self reference properties
c233f6922d is described below

commit c233f6922db4adb758bd65529e41fab3ecffd5c8
Author: Raymond Augé <ro...@apache.org>
AuthorDate: Wed Apr 6 18:37:26 2022 -0400

    FELIX-6518 - Enable interpolation plugin to self reference properties
    
    Signed-off-by: Raymond Augé <ro...@apache.org>
---
 configadmin-plugins/interpolation/README.md        | 13 +++++-
 .../InterpolationConfigurationPlugin.java          | 50 ++++++++++++++++++--
 .../InterpolationConfigurationPluginTest.java      | 53 ++++++++++++++++++----
 3 files changed, 99 insertions(+), 17 deletions(-)

diff --git a/configadmin-plugins/interpolation/README.md b/configadmin-plugins/interpolation/README.md
index c71165b78e..620b349174 100644
--- a/configadmin-plugins/interpolation/README.md
+++ b/configadmin-plugins/interpolation/README.md
@@ -11,8 +11,9 @@ Supported sources:
 * Environment variables
 * Framework properties
 * System properties
+* Other properties within the same configuration (self references)
 
-## Usage with Secret Files
+## Interpolating Secret Files
 
 Usually secrets (for example when provided by Kubernetes) will surface as files at a certain mount point, e.g.:
 
@@ -51,7 +52,7 @@ com.my.userinfo:
 "greeting": "Hello $[env:USER]!"
 ```
 
-## Interpolating properties
+## Interpolating Framework/System properties
 
 Properties can also be interpolated in the configuration. The properties values are
 obtained through the `BundleContext.getProperty(key)` API, which will return the framework
@@ -60,6 +61,14 @@ with this key is returned.
 
 Property values are obtained through the `$[prop:my.property]` syntax.
 
+## Interpolating configuration properties (self references)
+
+Interpolated values can also come from other values in the configuration itself.
+
+Configuration values are obtained through the `$[conf:my.property]` syntax.
+
+*Note:* Cycles are prevented and logged.
+
 ## Default Values
 
 It is possible to specify a default value as part of the placeholder, for example:
diff --git a/configadmin-plugins/interpolation/src/main/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPlugin.java b/configadmin-plugins/interpolation/src/main/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPlugin.java
index e8d456a0ca..f2e7b67372 100644
--- a/configadmin-plugins/interpolation/src/main/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPlugin.java
+++ b/configadmin-plugins/interpolation/src/main/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPlugin.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Dictionary;
@@ -47,6 +48,8 @@ class InterpolationConfigurationPlugin implements ConfigurationPlugin {
 
     private static final String TYPE_SECRET = "secret";
 
+    private static final String TYPE_CONF = "conf";
+
     private static final String DIRECTIVE_TYPE = "type";
 
     /** Delimiter for splitting up a single value into an array. */
@@ -126,7 +129,7 @@ class InterpolationConfigurationPlugin implements ConfigurationPlugin {
             String key = keys.nextElement();
             Object val = properties.get(key);
             if (val instanceof String) {
-                Object newVal = getNewValue(key, (String) val, pid);
+                Object newVal = getNewValue(key, (String) val, pid, properties);
                 if (newVal != null && !newVal.equals(val)) {
                     properties.put(key, newVal);
                     getLog().info("Replaced value of configuration property '{}' for PID {}", key, pid);
@@ -135,7 +138,7 @@ class InterpolationConfigurationPlugin implements ConfigurationPlugin {
                 String[] array = (String[]) val;
                 String[] newArray = null;
                 for (int i = 0; i < array.length; i++) {
-                    Object newVal = getNewValue(key, array[i], pid);
+                    Object newVal = getNewValue(key, array[i], pid, properties);
                     if (newVal != null && !newVal.equals(array[i])) {
                         if (newArray == null) {
                             newArray = new String[array.length];
@@ -152,15 +155,15 @@ class InterpolationConfigurationPlugin implements ConfigurationPlugin {
         }
     }
 
-    private Object getNewValue(final String key, final String value, final Object pid) {
-        final Object result = replace(key, value, pid);
+    private Object getNewValue(final String key, final String value, final Object pid, final Dictionary<String, Object> properties) {
+        final Object result = replace(key, value, pid, properties);
         if (value.equals(result)) {
             return null;
         }
         return result;
     }
 
-    Object replace(final String key, final String value, final Object pid) {
+    Object replace(final String key, final String value, final Object pid, final Dictionary<String, Object> properties) {
         final Object result = Interpolator.replace(value, (type, name, dir) -> {
             String v = null;
             if (TYPE_ENV.equals(type)) {
@@ -171,6 +174,9 @@ class InterpolationConfigurationPlugin implements ConfigurationPlugin {
 
             } else if (TYPE_SECRET.equals(type)) {
                 v = getVariableFromFile(key, name, pid);
+
+            } else if (TYPE_CONF.equals(type)) {
+                v = getVariableFromConfiguration(name, properties);
             }
             if (v == null) {
                 v = dir.get(DIRECTIVE_DEFAULT);
@@ -183,6 +189,40 @@ class InterpolationConfigurationPlugin implements ConfigurationPlugin {
         return result;
     }
 
+    String getVariableFromConfiguration(final String name, final Dictionary<String, Object> properties) {
+        Object val = properties.get(name);
+        String result;
+        if (val.getClass().isArray()) {
+            if (val instanceof int[]) {
+                result = Arrays.toString((int[])val);
+            } else if (val instanceof long[]) {
+                result = Arrays.toString((long[])val);
+            } else if (val instanceof float[]) {
+                result = Arrays.toString((float[])val);
+            } else if (val instanceof double[]) {
+                result = Arrays.toString((double[])val);
+            } else if (val instanceof byte[]) {
+                result = Arrays.toString((byte[])val);
+            } else if (val instanceof short[]) {
+                result = Arrays.toString((short[])val);
+            } else if (val instanceof boolean[]) {
+                result = Arrays.toString((boolean[])val);
+            } else if (val instanceof char[]) {
+                result = Arrays.toString((char[])val);
+            } else {
+                result =Arrays.toString((Object[])val);
+            }
+        } else {
+            result = String.valueOf(val);
+        }
+        // prevent circular references
+        if (result.startsWith("$[conf:")) {
+            getLog().warn("There is a cycle in '{}' for PID {}, returning {}", name, properties.get(Constants.SERVICE_PID), name);
+            return name;
+        }
+        return result;
+    }
+
     String getVariableFromEnvironment(final String name) {
         return System.getenv(name);
     }
diff --git a/configadmin-plugins/interpolation/src/test/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPluginTest.java b/configadmin-plugins/interpolation/src/test/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPluginTest.java
index e078fc1a98..4fdeab038d 100644
--- a/configadmin-plugins/interpolation/src/test/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPluginTest.java
+++ b/configadmin-plugins/interpolation/src/test/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPluginTest.java
@@ -30,6 +30,39 @@ import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 
 public class InterpolationConfigurationPluginTest {
+    @Test
+    public void testSelfReferenceConfiguration() throws Exception {
+        BundleContext bc = Mockito.mock(BundleContext.class);
+        Mockito.when(bc.getProperty("foo.bar")).thenReturn("hello there");
+
+        InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc::getProperty, null, null);
+
+        Dictionary<String, Object> dict = new Hashtable<>();
+        dict.put("someprop", "$[prop:foo.bar]");
+        dict.put("nope", "$[blah:blah]");
+        dict.put("self.reference", "$[conf:someprop] foo");
+        dict.put("long.array", new long[] {3L, 250000L});
+        dict.put("self.long.array", "$[conf:long.array]");
+        dict.put("string.array", new String[] {"foo", "bar"});
+        dict.put("self.string.array", "$[conf:string.array]");
+        dict.put("char.array", new char[] {'c', '香'});
+        dict.put("self.char.array", "$[conf:char.array]");
+        dict.put("self.circular.reference", "$[conf:self.circular.reference]");
+        dict.put("self.circular.reference.a", "$[conf:self.circular.reference.b]");
+        dict.put("self.circular.reference.b", "$[conf:self.circular.reference.a]");
+
+        plugin.modifyConfiguration(null, dict);
+        assertEquals("hello there", dict.get("someprop"));
+        assertEquals("$[blah:blah]", dict.get("nope"));
+        assertEquals("hello there foo", dict.get("self.reference"));
+        assertEquals("[3, 250000]", dict.get("self.long.array"));
+        assertEquals("[foo, bar]", dict.get("self.string.array"));
+        assertEquals("[c, 香]", dict.get("self.char.array"));
+        assertEquals("self.circular.reference", dict.get("self.circular.reference"));
+        assertEquals("self.circular.reference.a", dict.get("self.circular.reference.a"));
+        assertEquals("self.circular.reference.a", dict.get("self.circular.reference.b"));
+    }
+
     @Test
     public void testModifyConfiguration() throws Exception {
         String envUser = System.getenv("USER");
@@ -119,9 +152,9 @@ public class InterpolationConfigurationPluginTest {
         InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(null,
                 file.getParent() + "," + file.getParentFile().getParent(), null);
 
-        assertEquals("xxla la layy", plugin.replace("akey", "xx$[secret:testfile.txt]yy", "apid"));
+        assertEquals("xxla la layy", plugin.replace("akey", "xx$[secret:testfile.txt]yy", "apid", new Hashtable<>()));
         String doesNotReplace = "xx$[" + rf + "]yy";
-        assertEquals(doesNotReplace, plugin.replace("akey", doesNotReplace, "apid"));
+        assertEquals(doesNotReplace, plugin.replace("akey", doesNotReplace, "apid", new Hashtable<>()));
     }
 
     @Test
@@ -131,7 +164,7 @@ public class InterpolationConfigurationPluginTest {
         InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(null,
                 file.getParent() + "," + file.getParentFile().getParent(), null);
 
-        assertEquals("foo", plugin.replace("akey", "foo", "apid"));
+        assertEquals("foo", plugin.replace("akey", "foo", "apid", new Hashtable<>()));
     }
 
     @Test
@@ -208,10 +241,10 @@ public class InterpolationConfigurationPluginTest {
                 file.getParent() + "," + file.getParentFile().getParent(), null);
 
         assertEquals("xxhello thereyyhello therezz",
-                plugin.replace("akey", "xx$[prop:foo.bar]yy$[prop:foo.bar]zz", "apid"));
+                plugin.replace("akey", "xx$[prop:foo.bar]yy$[prop:foo.bar]zz", "apid", new Hashtable<>()));
 
         assertEquals("xxla la layyhello therezz",
-                plugin.replace("akey", "xx$[secret:testfile.txt]yy$[prop:foo.bar]zz", "apid"));
+                plugin.replace("akey", "xx$[secret:testfile.txt]yy$[prop:foo.bar]zz", "apid", new Hashtable<>()));
     }
 
     @Test
@@ -223,10 +256,10 @@ public class InterpolationConfigurationPluginTest {
                 file.getParent() + "," + file.getParentFile().getParent(), null);
 
         assertEquals("xxhello thereyyhello therezz",
-                plugin.replace("akey", "xx$[secret:foo.bar]yy$[secret:foo.bar]zz", "apid"));
+                plugin.replace("akey", "xx$[secret:foo.bar]yy$[secret:foo.bar]zz", "apid", new Hashtable<>()));
 
         assertEquals("xxla la layyhello therezz",
-                plugin.replace("akey", "xx$[secret:testfile.txt]yy$[secret:foo.bar]zz", "apid"));
+                plugin.replace("akey", "xx$[secret:testfile.txt]yy$[secret:foo.bar]zz", "apid", new Hashtable<>()));
     }
 
     @Test
@@ -236,7 +269,7 @@ public class InterpolationConfigurationPluginTest {
         Mockito.when(bc.getProperty("key")).thenReturn("foo.bar");
         InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc::getProperty, null, null);
 
-        assertEquals("hello there", plugin.replace("akey", "$[prop:$[prop:key]]", "apid"));
+        assertEquals("hello there", plugin.replace("akey", "$[prop:$[prop:key]]", "apid", new Hashtable<>()));
     }
 
     @Test
@@ -256,9 +289,9 @@ public class InterpolationConfigurationPluginTest {
         InterpolationConfigurationPlugin plugin = new InterpolationConfigurationPlugin(bc::getProperty, null, null);
 
         assertArrayEquals(new Integer[] { 2000, 3000 },
-                (Integer[]) plugin.replace("key", "$[prop:foo;type=Integer[];delimiter=,]", "somepid"));
+                (Integer[]) plugin.replace("key", "$[prop:foo;type=Integer[];delimiter=,]", "somepid", new Hashtable<>()));
         assertArrayEquals(new Integer[] { 1, 2 },
-                (Integer[]) plugin.replace("key", "$[prop:bar;type=Integer[];delimiter=,;default=1,2]", "somepid"));
+                (Integer[]) plugin.replace("key", "$[prop:bar;type=Integer[];delimiter=,;default=1,2]", "somepid", new Hashtable<>()));
     }
 
     @Test