Retain JSON data type in PropertySource.

We now retain the JSON data type in the property source. This change requires property transformers to accept/produce a Map of String to Object whereas it was String to String before.

See gh-169.
This commit is contained in:
Mark Paluch
2018-01-17 13:06:08 +01:00
parent 49079a8a39
commit f4d05a4fb6
8 changed files with 105 additions and 53 deletions

View File

@@ -60,7 +60,7 @@ public class LeaseAwareVaultPropertySource
private final RequestedSecret requestedSecret;
private final Map<String, String> properties = new ConcurrentHashMap<>();
private final Map<String, Object> properties = new ConcurrentHashMap<>();
private final PropertyTransformer propertyTransformer;
@@ -174,7 +174,7 @@ public class LeaseAwareVaultPropertySource
* @param properties reference to property storage of this property source.
*/
protected void handleLeaseEvent(SecretLeaseEvent leaseEvent,
Map<String, String> properties) {
Map<String, Object> properties) {
if (leaseEvent.getSource() != getRequestedSecret()) {
return;
@@ -189,7 +189,7 @@ public class LeaseAwareVaultPropertySource
if (leaseEvent instanceof SecretLeaseCreatedEvent) {
SecretLeaseCreatedEvent created = (SecretLeaseCreatedEvent) leaseEvent;
properties.putAll(doTransformProperties(toStringMap(created.getSecrets())));
properties.putAll(doTransformProperties(flattenMap(created.getSecrets())));
}
}
@@ -199,18 +199,32 @@ public class LeaseAwareVaultPropertySource
* @param properties must not be {@literal null}.
* @return the transformed properties.
*/
protected Map<String, String> doTransformProperties(Map<String, String> properties) {
protected Map<String, Object> doTransformProperties(Map<String, Object> properties) {
return this.propertyTransformer.transformProperties(properties);
}
/**
* Utility method converting a {@code String/Object} map to a {@code String/String}
* map.
* Utility method converting a {@code String/Object} map to a flat
* {@code String/String} map. Nested objects are represented with property paths.
*
* @param data the map
* @return the flattened map.
* @deprecated since 2.0, use {@link #flattenMap(Map)} to retain JSON data types.
*/
@Deprecated
protected Map<String, String> toStringMap(Map<String, Object> data) {
return JsonMapFlattener.flattenToStringMap(data);
}
/**
* Utility method converting a {@code String/Object} map to a flat
* {@code String/Object} map. Nested objects are represented with property path keys.
*
* @param data the map
* @return the flattened map.
* @since 2.0
*/
protected Map<String, Object> flattenMap(Map<String, Object> data) {
return JsonMapFlattener.flatten(data);
}
}

View File

@@ -50,7 +50,7 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultOperation
private final String path;
private final Map<String, String> properties = new LinkedHashMap<>();
private final Map<String, Object> properties = new LinkedHashMap<>();
private final PropertyTransformer propertyTransformer;
@@ -122,7 +122,7 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultOperation
logger.debug(String.format("Fetching properties from Vault at %s", path));
}
Map<String, String> properties = doGetProperties(path);
Map<String, Object> properties = doGetProperties(path);
if (properties != null) {
this.properties.putAll(doTransformProperties(properties));
@@ -154,7 +154,7 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultOperation
* @throws VaultException on problems retrieving properties
*/
@Nullable
protected Map<String, String> doGetProperties(String path) throws VaultException {
protected Map<String, Object> doGetProperties(String path) throws VaultException {
VaultResponse vaultResponse = this.source.read(path);
@@ -166,7 +166,7 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultOperation
return null;
}
return toStringMap(vaultResponse.getData());
return flattenMap(vaultResponse.getData());
}
/**
@@ -175,18 +175,32 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultOperation
* @param properties must not be {@literal null}.
* @return the transformed properties.
*/
protected Map<String, String> doTransformProperties(Map<String, String> properties) {
protected Map<String, Object> doTransformProperties(Map<String, Object> properties) {
return this.propertyTransformer.transformProperties(properties);
}
/**
* Utility method converting a {@code String/Object} map to a {@code String/String}
* map.
* Utility method converting a {@code String/Object} map to a flat
* {@code String/String} map.
*
* @param data the map
* @return the flattened map.
* @deprecated since 2.0, use {@link #flattenMap(Map)} to retain JSON data types.
*/
@Deprecated
protected Map<String, String> toStringMap(Map<String, Object> data) {
return JsonMapFlattener.flattenToStringMap(data);
}
/**
* Utility method converting a {@code String/Object} map to a flat
* {@code String/Object} map. Nested objects are represented with property path keys.
*
* @param data the map
* @return the flattened map.
* @since 2.0
*/
protected Map<String, Object> flattenMap(Map<String, Object> data) {
return JsonMapFlattener.flatten(data);
}
}

View File

@@ -38,7 +38,7 @@ public interface PropertyTransformer {
* @param input must not be {@literal null}.
* @return transformed properties.
*/
Map<String, String> transformProperties(Map<String, String> input);
Map<String, Object> transformProperties(Map<String, Object> input);
/**
* Return a composed transformer function that first applies this filter, and then

View File

@@ -66,10 +66,9 @@ public abstract class PropertyTransformers {
return new PropertyTransformerSupport() {
@Override
public Map<String, String> transformProperties(
Map<String, String> input) {
public Map<String, Object> transformProperties(Map<String, Object> input) {
Map<String, String> processed = that.transformProperties(input);
Map<String, Object> processed = that.transformProperties(input);
return after.transformProperties(processed);
}
};
@@ -95,7 +94,7 @@ public abstract class PropertyTransformers {
}
@Override
public Map<String, String> transformProperties(Map<String, String> input) {
public Map<String, Object> transformProperties(Map<String, Object> input) {
return input;
}
}
@@ -118,12 +117,12 @@ public abstract class PropertyTransformers {
}
@Override
public Map<String, String> transformProperties(Map<String, String> input) {
public Map<String, Object> transformProperties(Map<String, Object> input) {
Map<String, String> target = new LinkedHashMap<>(input.size(),
Map<String, Object> target = new LinkedHashMap<>(input.size(),
1);
for (Entry<String, String> entry : input.entrySet()) {
for (Entry<String, Object> entry : input.entrySet()) {
if (entry.getValue() == null) {
continue;
@@ -163,12 +162,12 @@ public abstract class PropertyTransformers {
}
@Override
public Map<String, String> transformProperties(Map<String, String> input) {
public Map<String, Object> transformProperties(Map<String, Object> input) {
Map<String, String> target = new LinkedHashMap<>(input.size(),
Map<String, Object> target = new LinkedHashMap<>(input.size(),
1);
for (Entry<String, String> entry : input.entrySet()) {
for (Entry<String, Object> entry : input.entrySet()) {
target.put(propertyNamePrefix + entry.getKey(), entry.getValue());
}

View File

@@ -19,6 +19,8 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -37,20 +39,17 @@ import org.springframework.util.StringUtils;
* <p>
* Input:
*
* <pre>
* <code>
* {"key": {"nested: "value"}, "another.key": ["one", "two"] }
* </code>
* <pre class="code">
* {"key": {"nested: 1}, "another.key": ["one", "two"] }
* </pre>
*
* <br>
* Result
*
* <pre>
* <code> key.nested=value
* <pre class="code">
* key.nested=1
* another.key[0]=one
* another.key[1]=two
* </code>
* </pre>
*
* @author Mark Paluch
@@ -67,19 +66,42 @@ public abstract class JsonMapFlattener {
* @param inputMap must not be {@literal null}.
* @return the resulting {@link Map}.
*/
public static Map<String, String> flatten(Map<String, ? extends Object> inputMap) {
public static Map<String, Object> flatten(Map<String, ? extends Object> inputMap) {
Assert.notNull(inputMap, "Input Map must not be null");
Map<String, Object> resultMap = new LinkedHashMap<>();
doFlatten("", inputMap.entrySet().iterator(), resultMap, UnaryOperator.identity());
return resultMap;
}
/**
* Flatten a hierarchical {@link Map} into a flat {@link Map} with key names using
* property dot notation.
*
* @param inputMap must not be {@literal null}.
* @return the resulting {@link Map}.
* @since 2.0
*/
public static Map<String, String> flattenToStringMap(
Map<String, ? extends Object> inputMap) {
Assert.notNull(inputMap, "Input Map must not be null");
Map<String, String> resultMap = new LinkedHashMap<>();
doFlatten("", inputMap.entrySet().iterator(), resultMap);
doFlatten("", inputMap.entrySet().iterator(), resultMap, it -> it == null ? null
: it.toString());
return resultMap;
}
private static void doFlatten(String propertyPrefix,
Iterator<? extends Entry<String, ?>> inputMap, Map<String, String> resultMap) {
Iterator<? extends Entry<String, ?>> inputMap,
Map<String, ? extends Object> resultMap,
Function<Object, Object> valueTransformer) {
if (StringUtils.hasText(propertyPrefix)) {
propertyPrefix = propertyPrefix + ".";
@@ -89,35 +111,38 @@ public abstract class JsonMapFlattener {
Entry<String, ? extends Object> entry = inputMap.next();
flattenElement(propertyPrefix.concat(entry.getKey()), entry.getValue(),
resultMap);
resultMap, valueTransformer);
}
}
@SuppressWarnings("unchecked")
private static void flattenElement(String propertyPrefix, @Nullable Object source,
Map<String, String> resultMap) {
Map<String, ?> resultMap, Function<Object, Object> valueTransformer) {
if (source instanceof Iterable) {
flattenCollection(propertyPrefix, (Iterable<Object>) source, resultMap);
flattenCollection(propertyPrefix, (Iterable<Object>) source, resultMap,
valueTransformer);
return;
}
if (source instanceof Map) {
doFlatten(propertyPrefix, ((Map<String, ?>) source).entrySet().iterator(),
resultMap);
resultMap, valueTransformer);
return;
}
resultMap.put(propertyPrefix, source == null ? null : source.toString());
((Map) resultMap).put(propertyPrefix, valueTransformer.apply(source));
}
private static void flattenCollection(String propertyPrefix,
Iterable<Object> iterable, Map<String, String> resultMap) {
Iterable<Object> iterable, Map<String, ?> resultMap,
Function<Object, Object> valueTransformer) {
int counter = 0;
for (Object element : iterable) {
flattenElement(propertyPrefix + "[" + counter + "]", element, resultMap);
flattenElement(propertyPrefix + "[" + counter + "]", element, resultMap,
valueTransformer);
counter++;
}
}

View File

@@ -63,7 +63,7 @@ public class VaultPropertySourceUnitTests {
vaultTemplate, "secret/myapp", PropertyTransformers.noop());
assertThat(vaultPropertySource.getProperty("key")).isEqualTo("value");
assertThat(vaultPropertySource.getProperty("integer")).isEqualTo("1");
assertThat(vaultPropertySource.getProperty("integer")).isEqualTo(1);
assertThat(vaultPropertySource.getProperty("complex.key")).isEqualTo("value");
assertThat(vaultPropertySource.getProperty("empty")).isNull();
assertThat(vaultPropertySource.containsProperty("empty")).isFalse();
@@ -82,7 +82,7 @@ public class VaultPropertySourceUnitTests {
assertThat(vaultPropertySource.containsProperty("key")).isFalse();
assertThat(vaultPropertySource.getProperty("database.key")).isEqualTo("value");
assertThat(vaultPropertySource.getProperty("key")).isNull();
assertThat(vaultPropertySource.getProperty("database.integer")).isEqualTo("1");
assertThat(vaultPropertySource.getProperty("database.integer")).isEqualTo(1);
assertThat(vaultPropertySource.getProperty("database.complex.key")).isEqualTo(
"value");
}

View File

@@ -27,7 +27,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class PropertyTransformersUnitTests {
Map<String, String> properties = Collections.singletonMap("key", "value");
Map<String, Object> properties = Collections.singletonMap("key", "value");
@Test
public void propertyNamePrefix() {

View File

@@ -34,9 +34,9 @@ public class JsonMapFlattenerUnitTests {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Test
public void shouldPreserveFlatMap() throws Exception {
public void shouldPreserveFlatMap() {
Map<String, String> result = JsonMapFlattener.flatten(Collections.singletonMap(
Map<String, Object> result = JsonMapFlattener.flatten(Collections.singletonMap(
"key", "value"));
assertThat(result).containsEntry("key", "value");
}
@@ -45,10 +45,10 @@ public class JsonMapFlattenerUnitTests {
public void shouldFlattenNestedObject() throws Exception {
Map<String, Object> map = objectMapper.readValue(
"{\"key\": { \"nested\":\"value\"} }", Map.class);
Map<String, String> result = JsonMapFlattener.flatten(map);
"{\"key\": { \"nested\":true} }", Map.class);
Map<String, Object> result = JsonMapFlattener.flatten(map);
assertThat(result).containsEntry("key.nested", "value");
assertThat(result).containsEntry("key.nested", true);
}
@Test
@@ -56,7 +56,7 @@ public class JsonMapFlattenerUnitTests {
Map<String, Object> map = objectMapper.readValue(
"{\"key\": { \"nested\": {\"anotherLevel\": \"value\"} } }", Map.class);
Map<String, String> result = JsonMapFlattener.flatten(map);
Map<String, Object> result = JsonMapFlattener.flatten(map);
assertThat(result).containsEntry("key.nested.anotherLevel", "value");
}
@@ -67,7 +67,7 @@ public class JsonMapFlattenerUnitTests {
Map<String, Object> map = objectMapper.readValue(
"{\"key\": [\"one\", \"two\"], \"dotted.key\": [\"one\", \"two\"] }",
Map.class);
Map<String, String> result = JsonMapFlattener.flatten(map);
Map<String, Object> result = JsonMapFlattener.flatten(map);
assertThat(result).containsEntry("key[0]", "one").containsEntry("key[1]", "two");
assertThat(result).containsEntry("dotted.key[0]", "one").containsEntry(
@@ -80,7 +80,7 @@ public class JsonMapFlattenerUnitTests {
Map<String, Object> map = objectMapper.readValue(
"{\"key\": [{ \"nested\":\"value\"}, { \"nested\":\"other-value\"}] }",
Map.class);
Map<String, String> result = JsonMapFlattener.flatten(map);
Map<String, Object> result = JsonMapFlattener.flatten(map);
assertThat(result).containsEntry("key[0].nested", "value").containsEntry(
"key[1].nested", "other-value");
@@ -93,7 +93,7 @@ public class JsonMapFlattenerUnitTests {
.readValue(
"{\"key\": { \"level1\": [{ \"nested\":\"value\"}, { \"nested\":\"other-value\"}]} }",
Map.class);
Map<String, String> result = JsonMapFlattener.flatten(map);
Map<String, Object> result = JsonMapFlattener.flatten(map);
assertThat(result).containsEntry("key.level1[0].nested", "value").containsEntry(
"key.level1[1].nested", "other-value");