From 76871fdf7c865da4cd355d2d8c539df5e89b9ad0 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 26 Feb 2015 18:11:33 +0000 Subject: [PATCH] Fix YAML output if arrays are nested or contain objects Fixes gh-92 --- .../server/EncryptionController.java | 4 +- .../server/EnvironmentController.java | 54 ++++++++++++------- .../server/EnvironmentControllerTests.java | 41 ++++++++++++-- 3 files changed, 73 insertions(+), 26 deletions(-) diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/configure/server/EncryptionController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/configure/server/EncryptionController.java index 0f8ff4b3..57d8d0c2 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/configure/server/EncryptionController.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/configure/server/EncryptionController.java @@ -21,9 +21,9 @@ import java.net.URLDecoder; import java.security.KeyPair; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -225,7 +225,7 @@ public class EncryptionController { Environment result = new Environment(environment.getName(), environment.getLabel()); for (PropertySource source : environment.getPropertySources()) { - ConcurrentHashMap map = new ConcurrentHashMap( + Map map = new LinkedHashMap( source.getSource()); for (Entry entry : map.entrySet()) { Object key = entry.getKey(); diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/configure/server/EnvironmentController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/configure/server/EnvironmentController.java index 1b94e412..b99286ad 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/configure/server/EnvironmentController.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/configure/server/EnvironmentController.java @@ -7,7 +7,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Properties; import javax.servlet.http.HttpServletResponse; @@ -16,6 +15,8 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.bind.PropertiesConfigurationFactory; import org.springframework.cloud.configure.Environment; import org.springframework.cloud.configure.PropertySource; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -30,7 +31,9 @@ import org.yaml.snakeyaml.Yaml; @RestController @RequestMapping("${spring.cloud.config.server.prefix:}") public class EnvironmentController { - + + private static final String MAP_PREFIX = "map"; + @Qualifier("MultipleJGitEnvironmentRepository") private EnvironmentRepository repository; @@ -63,7 +66,7 @@ public class EnvironmentController { } return environment; } - + @RequestMapping("/{name}-{profiles}.properties") public ResponseEntity properties(@PathVariable String name, @PathVariable String profiles) throws IOException { @@ -77,13 +80,14 @@ public class EnvironmentController { throw new IllegalArgumentException( "Properties output not supported for name or profiles containing hyphens"); } - Properties properties = convertToProperties(labelled(name, profiles, label)); + Map properties = convertToProperties(labelled(name, profiles, + label)); return getSuccess(sortLines(properties)); } - private String sortLines(Properties properties) throws IOException { + private String sortLines(Map properties) throws IOException { List list = new ArrayList(); - for (Entry entry : properties.entrySet()) { + for (Entry entry : properties.entrySet()) { if (entry.getKey().equals("spring.profiles")) { continue; } @@ -117,12 +121,18 @@ public class EnvironmentController { LinkedHashMap target = new LinkedHashMap(); PropertiesConfigurationFactory> factory = new PropertiesConfigurationFactory>( target); - Properties properties = convertToProperties(labelled(name, profiles, label)); + Map data = convertToProperties(labelled(name, profiles, + label)); + LinkedHashMap properties = new LinkedHashMap(); + for (String key : data.keySet()) { + properties.put(MAP_PREFIX + "." + key, data.get(key)); + } addArrays(target, properties); - factory.setProperties(properties); + MutablePropertySources propertySources = new MutablePropertySources(); + propertySources.addFirst(new MapPropertySource("properties", properties)); + factory.setPropertySources(propertySources); factory.bindPropertiesToTarget(); - Map input = factory.getObject(); - return getSuccess(new Yaml().dumpAsMap(input)); + return getSuccess(new Yaml().dumpAsMap(target.get(MAP_PREFIX))); } @ExceptionHandler(IllegalArgumentException.class) @@ -145,13 +155,14 @@ public class EnvironmentController { * @param target the target Map * @param properties the properties (with key names to check) */ - private void addArrays(LinkedHashMap target, Properties properties) { - for (String key : properties.stringPropertyNames()) { + private void addArrays(LinkedHashMap target, + Map properties) { + for (String key : properties.keySet()) { int index = key.indexOf("["); Map current = target; if (index > 0) { String stem = key.substring(0, index); - String[] keys = StringUtils.split(stem, "."); + String[] keys = StringUtils.delimitedListToStringArray(stem, "."); for (int i = 0; i < keys.length - 1; i++) { if (current.get(keys[i]) == null) { LinkedHashMap map = new LinkedHashMap(); @@ -174,24 +185,27 @@ public class EnvironmentController { int position = Integer .valueOf(key.substring(index + 1, key.indexOf("]"))); while (position >= value.size()) { - value.add(""); + if (key.indexOf("].", index) > 0) { + value.add(new LinkedHashMap()); + } + else { + value.add(""); + } } } } } - private Properties convertToProperties(Environment profiles) { - Properties map = new Properties(); - List sources = new ArrayList(profiles.getPropertySources()); + private Map convertToProperties(Environment profiles) { + Map map = new LinkedHashMap(); + List sources = new ArrayList( + profiles.getPropertySources()); Collections.reverse(sources); for (PropertySource source : sources) { @SuppressWarnings("unchecked") Map value = (Map) source.getSource(); map.putAll(value); } - for (Entry entry : map.entrySet()) { - map.put(entry.getKey(), entry.getValue().toString()); - } return map; } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/configure/server/EnvironmentControllerTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/configure/server/EnvironmentControllerTests.java index 9f5f6b12..f72c6723 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/configure/server/EnvironmentControllerTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/configure/server/EnvironmentControllerTests.java @@ -28,9 +28,6 @@ import org.junit.rules.ExpectedException; import org.mockito.Mockito; import org.springframework.cloud.configure.Environment; import org.springframework.cloud.configure.PropertySource; -import org.springframework.cloud.configure.server.EncryptionController; -import org.springframework.cloud.configure.server.EnvironmentController; -import org.springframework.cloud.configure.server.EnvironmentRepository; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -65,7 +62,7 @@ public class EnvironmentControllerTests { @Test public void propertyOverrideInYaml() throws Exception { - Map map = new HashMap(); + Map map = new LinkedHashMap(); map.put("a.b.c", "d"); environment.add(new PropertySource("one", map)); environment.addFirst(new PropertySource("two", Collections.singletonMap("a.b.c", "e"))); @@ -85,6 +82,42 @@ public class EnvironmentControllerTests { assertEquals("a:\n b:\n - c\n - d\n", yaml); } + @Test + public void arrayOfObjectInYaml() throws Exception { + Map map = new LinkedHashMap(); + map.put("a.b[0].c", "d"); + map.put("a.b[0].d", "e"); + map.put("a.b[1].c", "d"); + environment.add(new PropertySource("one", map)); + Mockito.when(repository.findOne("foo", "bar", "master")).thenReturn(environment); + String yaml = controller.yaml("foo", "bar").getBody(); + assertEquals("a:\n b:\n - d: e\n c: d\n - c: d\n", yaml); + } + + @Test + public void arrayOfObjectAtTopLevelInYaml() throws Exception { + Map map = new LinkedHashMap(); + map.put("b[0].c", "d"); + map.put("b[0].d", "e"); + map.put("b[1].c", "d"); + environment.add(new PropertySource("one", map)); + Mockito.when(repository.findOne("foo", "bar", "master")).thenReturn(environment); + String yaml = controller.yaml("foo", "bar").getBody(); + assertEquals("b:\n- c: d\n d: e\n- c: d\n", yaml); + } + + @Test + public void arrayOfObjectNestedLevelInYaml() throws Exception { + Map map = new LinkedHashMap(); + map.put("x.a.b[0].c", "d"); + map.put("x.a.b[0].d", "e"); + map.put("x.a.b[1].c", "d"); + environment.add(new PropertySource("one", map)); + Mockito.when(repository.findOne("foo", "bar", "master")).thenReturn(environment); + String yaml = controller.yaml("foo", "bar").getBody(); + assertEquals("x:\n a:\n b:\n - c: d\n d: e\n - c: d\n", yaml); + } + @Test public void mappingForEnvironment() throws Exception { Mockito.when(repository.findOne("foo", "bar", "master")).thenReturn(environment);