From 542dede363633e1ea14fb10dba911d1ca1a6b27f Mon Sep 17 00:00:00 2001 From: Kirill Cherednik Date: Tue, 25 Feb 2020 17:51:35 +0300 Subject: [PATCH 1/3] Text properties substitution --- .../core/utils/PropertiesSubstitutor.java | 106 ++++++++++++++++++ .../api/repository/client/v1/APIPull.java | 3 + 2 files changed, 109 insertions(+) create mode 100644 core/src/main/java/com/confighub/core/utils/PropertiesSubstitutor.java diff --git a/core/src/main/java/com/confighub/core/utils/PropertiesSubstitutor.java b/core/src/main/java/com/confighub/core/utils/PropertiesSubstitutor.java new file mode 100644 index 0000000..deb9ca3 --- /dev/null +++ b/core/src/main/java/com/confighub/core/utils/PropertiesSubstitutor.java @@ -0,0 +1,106 @@ +package com.confighub.core.utils; + +import com.confighub.core.repository.Property; +import com.confighub.core.repository.PropertyKey; +import com.confighub.core.security.SecurityProfile; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; +import java.util.regex.Pattern; + +public class PropertiesSubstitutor { + + private static final Logger log = LogManager.getLogger(PropertiesSubstitutor.class); + + private static String replaceKey(String key, String value, String text) + { + return text.replaceAll("\\$\\{" + Pattern.quote(key) + "}", value); + } + + private static boolean containsKey(String key, String text) + { + return text.contains("${" + key + "}"); + } + + public static void substitute(final Map resolved, + final Map passwords) { + Map textPropertiesValues = new HashMap<>(); + for (PropertyKey key : resolved.keySet()) + { + //We are only interested in text properties + if(key.getValueDataType().equals(PropertyKey.ValueDataType.Text)) + { + Property property = resolved.get(key); + if(key.isEncrypted()) + { + SecurityProfile sp = key.getSecurityProfile(); + String password = passwords.get(sp.getName()); + if(password != null) { + property.decryptValue(password); + textPropertiesValues.put(key, property.getValue()); + } + } + else + { + textPropertiesValues.put(key, property.getValue()); + } + } + } + + Map> inboundDependencies = new HashMap<>(); + Map> outboundDependencies = new HashMap<>(); + Queue queue = new ArrayDeque<>(); + + for (PropertyKey key : textPropertiesValues.keySet()) + { + inboundDependencies.put(key, new HashSet<>()); + outboundDependencies.put(key, new HashSet<>()); + } + + for (PropertyKey keyA : textPropertiesValues.keySet()) + { + String text = textPropertiesValues.get(keyA); + for (PropertyKey keyB : textPropertiesValues.keySet()) + { + if(containsKey(keyB.getKey(), text)) + { + outboundDependencies.get(keyA).add(keyB); + inboundDependencies.get(keyB).add(keyA); + } + } + } + + for (PropertyKey key : textPropertiesValues.keySet()) + { + inboundDependencies.put(key, new HashSet<>()); + if(outboundDependencies.get(key).isEmpty()) + { + queue.offer(key); + } + } + + while (!queue.isEmpty()) { + PropertyKey key = queue.poll(); + for(PropertyKey dependantKey : inboundDependencies.get(key)) { + textPropertiesValues.put( + dependantKey, + replaceKey( + key.getKey(), + textPropertiesValues.get(key), + textPropertiesValues.get(dependantKey))); + Set dependencies = outboundDependencies.get(dependantKey); + dependencies.remove(key); + if(dependencies.isEmpty()) { + queue.offer(dependantKey); + } + } + } + + //TODO: move to the calling method? + for (PropertyKey key : textPropertiesValues.keySet()) { + Property property = resolved.get(key); + property.setValue(textPropertiesValues.get(key)); + } + } +} diff --git a/rest/src/main/java/com/confighub/api/repository/client/v1/APIPull.java b/rest/src/main/java/com/confighub/api/repository/client/v1/APIPull.java index d041c7d..ae3129e 100644 --- a/rest/src/main/java/com/confighub/api/repository/client/v1/APIPull.java +++ b/rest/src/main/java/com/confighub/api/repository/client/v1/APIPull.java @@ -24,6 +24,7 @@ import com.confighub.core.security.SecurityProfile; import com.confighub.core.store.Store; import com.confighub.core.utils.FileUtils; +import com.confighub.core.utils.PropertiesSubstitutor; import com.confighub.core.utils.Utils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -198,6 +199,8 @@ public static JsonObject getConfiguration(final Repository repository, EnumSet depths = repository.getDepth().getDepths(); JsonObject config = new JsonObject(); + PropertiesSubstitutor.substitute(resolved, passwords); + if (!noFiles) { JsonObject filesJson = new JsonObject(); From 6a64a9b7fcedfce1b8f0e5c281aee2c97c52262b Mon Sep 17 00:00:00 2001 From: Kirill Cherednik Date: Tue, 25 Feb 2020 17:58:50 +0300 Subject: [PATCH 2/3] Version updated to 1.8.5-alpha-1 --- core/pom.xml | 2 +- pom.xml | 2 +- rest/pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index beb7109..73b8380 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -14,7 +14,7 @@ ConfigHub ConfigHub-Core - 1.8.4 + 1.8.5-alpha-1 ConfigHub Core diff --git a/pom.xml b/pom.xml index b554e5c..fe023ed 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ ConfigHub ConfigHub - 1.8.4 + 1.8.5-alpha-1 ConfigHub Parent pom diff --git a/rest/pom.xml b/rest/pom.xml index c84ed12..15cc84f 100644 --- a/rest/pom.xml +++ b/rest/pom.xml @@ -5,7 +5,7 @@ ConfigHub ConfigHub-Rest - 1.8.4 + 1.8.5-alpha-1 ConfigHub REST war @@ -14,7 +14,7 @@ ConfigHub ConfigHub-Core - 1.8.4 + 1.8.5-alpha-1 From 3731ceef4c6a3a1b3ce625d724a0612058b8357f Mon Sep 17 00:00:00 2001 From: Kirill Cherednik Date: Wed, 26 Feb 2020 15:48:32 +0300 Subject: [PATCH 3/3] PropertiesSubstitutor test --- .../core/utils/PropertiesSubstitutor.java | 11 +--- .../core/utils/PropertiesSubstitutorTest.java | 56 +++++++++++++++++++ rest/pom.xml | 2 +- .../api/repository/client/v1/APIPull.java | 9 ++- 4 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 core/src/test/java/com/confighub/core/utils/PropertiesSubstitutorTest.java diff --git a/core/src/main/java/com/confighub/core/utils/PropertiesSubstitutor.java b/core/src/main/java/com/confighub/core/utils/PropertiesSubstitutor.java index deb9ca3..49e6e4e 100644 --- a/core/src/main/java/com/confighub/core/utils/PropertiesSubstitutor.java +++ b/core/src/main/java/com/confighub/core/utils/PropertiesSubstitutor.java @@ -23,8 +23,8 @@ private static boolean containsKey(String key, String text) return text.contains("${" + key + "}"); } - public static void substitute(final Map resolved, - final Map passwords) { + public static Map resolveTextSubstitutions(final Map resolved, + final Map passwords) { Map textPropertiesValues = new HashMap<>(); for (PropertyKey key : resolved.keySet()) { @@ -73,7 +73,6 @@ public static void substitute(final Map resolved, for (PropertyKey key : textPropertiesValues.keySet()) { - inboundDependencies.put(key, new HashSet<>()); if(outboundDependencies.get(key).isEmpty()) { queue.offer(key); @@ -97,10 +96,6 @@ public static void substitute(final Map resolved, } } - //TODO: move to the calling method? - for (PropertyKey key : textPropertiesValues.keySet()) { - Property property = resolved.get(key); - property.setValue(textPropertiesValues.get(key)); - } + return textPropertiesValues; } } diff --git a/core/src/test/java/com/confighub/core/utils/PropertiesSubstitutorTest.java b/core/src/test/java/com/confighub/core/utils/PropertiesSubstitutorTest.java new file mode 100644 index 0000000..ed50aae --- /dev/null +++ b/core/src/test/java/com/confighub/core/utils/PropertiesSubstitutorTest.java @@ -0,0 +1,56 @@ +package com.confighub.core.utils; + +import com.confighub.core.repository.Depth; +import com.confighub.core.repository.Property; +import com.confighub.core.repository.PropertyKey; +import com.confighub.core.repository.Repository; +import com.confighub.core.user.Account; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class PropertiesSubstitutorTest { + + private static final Repository testRepository = + new Repository("TestRepository", Depth.D0, false, new Account()); + + private void addProperty(String key, String value, Map properties, Map keys) { + PropertyKey propertyKey = new PropertyKey(testRepository, key, PropertyKey.ValueDataType.Text); + Property property = new Property(testRepository); + try { + Field valueField = Property.class.getDeclaredField("value"); + valueField.setAccessible(true); + valueField.set(property, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + properties.put(propertyKey, property); + keys.put(key, propertyKey); + } + + @Test + public void substitutionBasicTest() + { + Map properties = new HashMap<>(); + Map keys = new HashMap<>(); + + addProperty("key0", "value0:${key1}", properties, keys); + addProperty("key1", "value1:${key0}:${key2}", properties, keys); + addProperty("key2", "value2", properties, keys); + addProperty("key3", "value3:${key2}", properties, keys); + addProperty("key4", "value4:${key3}", properties, keys); + + Map result = PropertiesSubstitutor.resolveTextSubstitutions(properties, new HashMap<>()); + + assertEquals(properties.size(), result.size()); + assertEquals("value0:${key1}", result.get(keys.get("key0"))); + assertEquals("value1:${key0}:value2", result.get(keys.get("key1"))); + assertEquals("value2", result.get(keys.get("key2"))); + assertEquals("value3:value2", result.get(keys.get("key3"))); + assertEquals("value4:value3:value2", result.get(keys.get("key4"))); + } +} diff --git a/rest/pom.xml b/rest/pom.xml index 15cc84f..582ba68 100644 --- a/rest/pom.xml +++ b/rest/pom.xml @@ -27,7 +27,7 @@ org.projectlombok lombok - 1.16.20 + 1.18.4 diff --git a/rest/src/main/java/com/confighub/api/repository/client/v1/APIPull.java b/rest/src/main/java/com/confighub/api/repository/client/v1/APIPull.java index ae3129e..1e42ce9 100644 --- a/rest/src/main/java/com/confighub/api/repository/client/v1/APIPull.java +++ b/rest/src/main/java/com/confighub/api/repository/client/v1/APIPull.java @@ -199,7 +199,14 @@ public static JsonObject getConfiguration(final Repository repository, EnumSet depths = repository.getDepth().getDepths(); JsonObject config = new JsonObject(); - PropertiesSubstitutor.substitute(resolved, passwords); + Map textPropertiesValues = + PropertiesSubstitutor.resolveTextSubstitutions(resolved, passwords); + + for (PropertyKey key : textPropertiesValues.keySet()) { + Property property = resolved.get(key); + property.setValue(textPropertiesValues.get(key)); + } + if (!noFiles) {