Author: cziegeler
Date: Thu Feb 27 11:11:59 2020
New Revision: 1874568

URL: http://svn.apache.org/viewvc?rev=1874568&view=rev
Log:
FELIX-6225 : Support splitting up value into array

Modified:
    felix/trunk/configadmin-plugins/interpolation/README.md
    
felix/trunk/configadmin-plugins/interpolation/src/main/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPlugin.java
    
felix/trunk/configadmin-plugins/interpolation/src/test/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPluginTest.java

Modified: felix/trunk/configadmin-plugins/interpolation/README.md
URL: 
http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/interpolation/README.md?rev=1874568&r1=1874567&r2=1874568&view=diff
==============================================================================
--- felix/trunk/configadmin-plugins/interpolation/README.md (original)
+++ felix/trunk/configadmin-plugins/interpolation/README.md Thu Feb 27 11:11:59 
2020
@@ -7,9 +7,9 @@ An OSGi Configuration Admin Plugin that
 * Framework properties
 * System properties
 
-## Usage with Kubernetes secrets
+## Usage with Secret Files
 
-The Kubernetes secrets will surface as file at a certain mountpoint, e.g.:
+Usually secrets (for example when provided by Kubernetes) will surface as 
files at a certain mount point, e.g.:
 
 ```
 $ ls /mnt/mysecrets
@@ -55,12 +55,51 @@ with this key is returned.
 
 Property values are obtained through the `$[prop:my.property]` syntax.
 
+## Default Values
+
+It is possible to specify a default value as part of the placeholder, for 
example:
+
+```
+"port" : "$[env:PORT;default=8080]"
+```
+
+Without a default, the placeholder is left in the value if no value can be 
found. With a default, the default is used instead.
+
+## Type Support
+
+A placeholder can contain additional information like the type the value 
should be converted to.
+
+
+```
+"port" : "$[env:PORT;type=Integer]"
+```
+
+In the above example, an Integer object with the port will be put into the 
Configuration instead of a String value.
+
+### Supported Scalar and Primitive Types
+
+The following types are supported: String, Integer, int, Long, long, Float, 
float, Double, double, Byte, byte, Short, short, Character, char, Boolean, 
boolean.
+
+### Supported Array Types
+
+The following array types are supported: String[], Integer[], int[], Long[], 
long[], Float[], float[], Double[], double[], Byte[], byte[], Short[], short[], 
Character[], char[], Boolean[], boolean[].
+
+A provided value (including the default value) can be split up into a string 
array before conversion by configuring a delimiter as part of the placeholder:
+
+```
+"ports" : "$[env:PORT;type=Integer[];delimiter=,;default=8080,8081]"
+```
+
 ## Configuration of the plugin
 
+The plugin (and Configuration Admin) can be controlled by various properties. 
These properties are
+framework properties and can be provided on initialization of the OSGi 
framework or as system properties
+as framework properties default to system properties.
+
 ### Consistent processing
 
-It is recommended to configure the ConfigAdmin to only start processing once 
this plugin is active. In case of
-the Felix ConfigAdmin implementation, this can be achieved by using the 
following property:
+It is recommended to configure the Configuration Admin to only start 
processing once this plugin is active. In case of
+the Apache Felix ConfigAdmin implementation, this can be achieved by using the 
following property:
 
 * `felix.cm.config.plugins`: 
`org.apache.felix.configadmin.plugin.interpolation`
 
@@ -73,6 +112,11 @@ This is done through the following prope
 
 * `org.apache.felix.configadmin.plugin.interpolation.secretsdir`: specify the 
directory where the files used for the file-based interpolation, such as 
Kubernetes secrets, are mounted.
 
-The property can be provided as an OSGi Framework property or alternatively as 
a Java System Property. 
-
 If the property is not present, the plugin will function, but without being 
able to replace values based on secrets.
+
+### File Encoding
+
+When reading files, for example secrets, the platform default encoding is 
used. The following property can be used to to control the reading:
+
+* `org.apache.felix.configadmin.plugin.interpolation.file.encoding` : specify 
the encoding to be used.
+

Modified: 
felix/trunk/configadmin-plugins/interpolation/src/main/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPlugin.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/interpolation/src/main/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPlugin.java?rev=1874568&r1=1874567&r2=1874568&view=diff
==============================================================================
--- 
felix/trunk/configadmin-plugins/interpolation/src/main/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPlugin.java
 (original)
+++ 
felix/trunk/configadmin-plugins/interpolation/src/main/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPlugin.java
 Thu Feb 27 11:11:59 2020
@@ -20,9 +20,12 @@ import java.io.File;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.osgi.framework.BundleContext;
@@ -42,6 +45,9 @@ class InterpolationConfigurationPlugin i
 
     private static final String DIRECTIVE_TYPE = "type";
 
+    /** Delimiter for splitting up a single value into an array. */
+    private static final String DIRECTIVE_DELIMITER = "delimiter";
+
     private static final String DIRECTIVE_DEFAULT = "default";
 
     private static final Map<String, Class<?>> TYPE_MAP = new HashMap<>();
@@ -166,7 +172,7 @@ class InterpolationConfigurationPlugin i
                 v = dir.get(DIRECTIVE_DEFAULT);
             }
             if (v != null && dir.containsKey(DIRECTIVE_TYPE)) {
-                return convertType(dir.get(DIRECTIVE_TYPE), v);
+                return convertType(dir.get(DIRECTIVE_TYPE), v, 
dir.get(DIRECTIVE_DELIMITER));
             }
             return v;
         });
@@ -216,17 +222,87 @@ class InterpolationConfigurationPlugin i
         return new String(bytes, this.encodingCharset).trim();
     }
 
-    private Object convertType(String type, String s) {
+    /**
+     * Convert the value to the given type
+     *
+     * @param type      The type (optional)
+     * @param value     The value
+     * @param delimiter The delimiter for array types (optional)
+     * @return The converted value
+     */
+    Object convertType(final String type, final String value, final String 
delimiter) {
         if (type == null) {
-            return s;
+            return value;
         }
 
         Class<?> cls = TYPE_MAP.get(type);
         if (cls != null) {
-            return Converters.standardConverter().convert(s).to(cls);
+            if ((cls.isArray() || Collection.class.isAssignableFrom(cls)) && 
delimiter != null) {
+                final String[] array = split(value, delimiter);
+                return Converters.standardConverter().convert(array).to(cls);
+            }
+            return Converters.standardConverter().convert(value).to(cls);
         }
 
         getLog().warn("Cannot convert to type: " + type);
-        return s;
+        return value;
+    }
+
+    /**
+     * Split a string into an array of strings. A backslash can be used to 
escape
+     * the delimiter (avoiding splitting), unless that backslash is preceded by
+     * another backslash, in which case the two backslashes are replaced by a 
single
+     * one and the value is split after the backslash.
+     * 
+     * @param value     The value to split
+     * @param delimiter The delimiter
+     * @return The resulting array
+     */
+    String[] split(String value, final String delimiter) {
+        List<String> result = null;
+        int start = -1;
+        while (start < value.length()) {
+            start = value.indexOf(delimiter, start + 1);
+            if (start == -1) {
+                // no delimiter found -> end
+                start = value.length();
+                if (result != null) {
+                    result.add(value);
+                }
+            } else {
+
+                boolean split = true;
+                if (start > 1 && value.charAt(start - 1) == 
Interpolator.ESCAPE) {
+                    if (start == 1 || value.charAt(start - 2) != 
Interpolator.ESCAPE) {
+                        split = false;
+                    } else if (value.charAt(start - 2) == Interpolator.ESCAPE) 
{
+                        value = value.substring(0, start - 
2).concat(value.substring(start - 1));
+                        start--;
+                    }
+                }
+
+                if (split) {
+                    if (result == null) {
+                        result = new ArrayList<String>();
+                    }
+                    result.add(value.substring(0, start));
+                    value = value.substring(start + delimiter.length());
+                    start = -1;
+                } else {
+                    if (start == 1) {
+                        value = value.substring(1);
+                    } else {
+                        value = value.substring(0, start - 
1).concat(value.substring(start));
+                        start--;
+                    }
+                }
+            }
+
+        }
+
+        if (result == null) {
+            return new String[] { value };
+        }
+        return result.toArray(new String[result.size()]);
     }
 }

Modified: 
felix/trunk/configadmin-plugins/interpolation/src/test/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPluginTest.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/configadmin-plugins/interpolation/src/test/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPluginTest.java?rev=1874568&r1=1874567&r2=1874568&view=diff
==============================================================================
--- 
felix/trunk/configadmin-plugins/interpolation/src/test/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPluginTest.java
 (original)
+++ 
felix/trunk/configadmin-plugins/interpolation/src/test/java/org/apache/felix/configadmin/plugin/interpolation/InterpolationConfigurationPluginTest.java
 Thu Feb 27 11:11:59 2020
@@ -16,6 +16,7 @@
  */
 package org.apache.felix.configadmin.plugin.interpolation;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
 import java.io.File;
@@ -197,4 +198,26 @@ public class InterpolationConfigurationP
 
         assertEquals("hello there", plugin.replace("akey", 
"$[prop:$[prop:key]]", "apid"));
     }
+
+    @Test
+    public void testArraySplit() throws Exception {
+        InterpolationConfigurationPlugin plugin = new 
InterpolationConfigurationPlugin(null, null, null);
+
+        assertArrayEquals(new String[] { "a,b,c" }, plugin.split("a,b,c", 
"."));
+        assertArrayEquals(new String[] { "a", "b", "c" }, 
plugin.split("a,b,c", ","));
+        assertArrayEquals(new String[] { "a,b", "c" }, plugin.split("a\\,b,c", 
","));
+        assertArrayEquals(new String[] { "a\\", "b", "c" }, 
plugin.split("a\\\\,b,c", ","));
+    }
+
+    @Test
+    public void testArrayTypeConversion() throws Exception {
+        BundleContext bc = Mockito.mock(BundleContext.class);
+        Mockito.when(bc.getProperty("foo")).thenReturn("2000,3000");
+        InterpolationConfigurationPlugin plugin = new 
InterpolationConfigurationPlugin(bc, null, null);
+
+        assertArrayEquals(new Integer[] { 2000, 3000 },
+                (Integer[]) plugin.replace("key", 
"$[prop:foo;type=Integer[];delimiter=,]", "somepid"));
+        assertArrayEquals(new Integer[] { 1, 2 },
+                (Integer[]) plugin.replace("key", 
"$[prop:bar;type=Integer[];delimiter=,;default=1,2]", "somepid"));
+    }
 }


Reply via email to