diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java index 7524fc95..0b61c29f 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java @@ -61,6 +61,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; * @author Bartosz Wojtkiewicz * @author Rafal Zukowski * @author Ivan Corrales Solera + * @author Daniel Frey * */ @RestController @@ -307,17 +308,45 @@ public class EnvironmentController { } } - private Map convertToProperties(Environment environment) { - Map map = new TreeMap<>(); - List sources = new ArrayList<>(environment.getPropertySources()); + private Map convertToProperties(Environment profiles) { + + // Map of unique keys containing full map of properties for each unique key + Map> map = new TreeMap<>(); + List sources = new ArrayList<>(profiles.getPropertySources()); Collections.reverse(sources); for (PropertySource source : sources) { + @SuppressWarnings("unchecked") - Map value = (Map) source.getSource(); - map.putAll(value); + Map value = (Map) source.getSource(); + for (String key : value.keySet()) { + + if (!key.contains("[")) { + + // Not an array, add unique key to the map + map.put(key, value); + + } + else { + + // An existing array might have already been added to the property map + // of an unequal size + // to the current array. Replace the array key in the current map + map.put(key.substring(0, key.indexOf('[') - 1), value); + } + } + } - postProcessProperties(map); - return map; + + // Combine all unique keys into a combined map + Map combinedMap = new TreeMap<>(); + for (Entry> entry : map.entrySet()) { + + combinedMap.putAll(entry.getValue()); + + } + + postProcessProperties(combinedMap); + return combinedMap; } private void postProcessProperties(Map propertiesMap) { diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java index a7b42f3a..a553c274 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java @@ -15,7 +15,12 @@ */ package org.springframework.cloud.config.server.environment; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.util.Collections; @@ -40,6 +45,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; * @author Dave Syer * @author Roy Clarkson * @author Ivan Corrales Solera + * @author Daniel Frey */ public class EnvironmentControllerTests { @@ -62,7 +68,8 @@ public class EnvironmentControllerTests { Map map = new HashMap(); map.put("a.b.c", "d"); this.environment.add(new PropertySource("one", map)); - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); String yaml = this.controller.yaml("foo", "bar", false).getBody(); assertEquals("a:\n b:\n c: d\n", yaml); } @@ -72,9 +79,10 @@ public class EnvironmentControllerTests { Map map = new LinkedHashMap(); map.put("a.b.c", "d"); this.environment.add(new PropertySource("one", map)); - this.environment.addFirst(new PropertySource("two", Collections.singletonMap("a.b.c", - "e"))); - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + this.environment.addFirst( + new PropertySource("two", Collections.singletonMap("a.b.c", "e"))); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); String yaml = this.controller.yaml("foo", "bar", false).getBody(); assertEquals("a:\n b:\n c: e\n", yaml); } @@ -92,17 +100,74 @@ public class EnvironmentControllerTests { map.put("a.b[0]", "c"); map.put("a.b[1]", "d"); this.environment.add(new PropertySource("one", map)); - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); String yaml = this.controller.yaml("foo", "bar", false).getBody(); assertEquals("a:\n b:\n - c\n - d\n", yaml); } + @Test + public void arrayOverridenInEnvironment() throws Exception { + // Add original values first source + Map oneMap = new LinkedHashMap(); + oneMap.put("a.b[0]", "c"); + oneMap.put("a.b[1]", "d"); + oneMap.put("a.b[2]", "z"); + this.environment.add(new PropertySource("one", oneMap)); + + // Add overridden values in second source + Map twoMap = new LinkedHashMap(); + twoMap.put("a.b[0]", "f"); + twoMap.put("a.b[1]", "h"); + this.environment.addFirst(new PropertySource("two", twoMap)); + + Mockito.when(this.repository.findOne("foo", "bar", "two")) + .thenReturn(this.environment); + Environment environment = this.controller.labelled("foo", "bar", "two"); + assertThat(environment, not(nullValue())); + assertThat(environment.getName(), equalTo("foo")); + assertThat(environment.getProfiles(), equalTo(new String[] { "master" })); + assertThat(environment.getLabel(), equalTo("master")); + assertThat(environment.getVersion(), nullValue()); + assertThat(environment.getPropertySources(), hasSize(2)); + assertThat(environment.getPropertySources().get(0).getName(), equalTo("two")); + assertThat(environment.getPropertySources().get(0).getSource().entrySet(), + hasSize(2)); + assertThat(environment.getPropertySources().get(1).getName(), equalTo("one")); + assertThat(environment.getPropertySources().get(1).getSource().entrySet(), + hasSize(3)); + } + + @Test + public void arrayOverridenInYaml() throws Exception { + // Add original values first source + Map oneMap = new LinkedHashMap(); + oneMap.put("a.b[0]", "c"); + oneMap.put("a.b[1]", "d"); + oneMap.put("a.b[2]", "z"); + this.environment.add(new PropertySource("one", oneMap)); + + // Add overridden values in second source + Map twoMap = new LinkedHashMap(); + twoMap.put("a.b[0]", "f"); + twoMap.put("a.b[1]", "h"); + this.environment.addFirst(new PropertySource("two", twoMap)); + + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); + String yaml = this.controller.yaml("foo", "bar", false).getBody(); + + // Result will not contain original, extra values from oneMap + assertEquals("a:\n b:\n - f\n - h\n", yaml); + } + @Test public void textAtTopLevelInYaml() throws Exception { Map map = new LinkedHashMap(); map.put("document", "blah"); this.environment.add(new PropertySource("one", map)); - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); String yaml = this.controller.yaml("foo", "bar", false).getBody(); assertEquals("blah\n", yaml); } @@ -113,7 +178,8 @@ public class EnvironmentControllerTests { map.put("document[0]", "c"); map.put("document[1]", "d"); this.environment.add(new PropertySource("one", map)); - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); String yaml = this.controller.yaml("foo", "bar", false).getBody(); assertEquals("- c\n- d\n", yaml); } @@ -124,7 +190,8 @@ public class EnvironmentControllerTests { map.put("document[0].a", "c"); map.put("document[1].a", "d"); this.environment.add(new PropertySource("one", map)); - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); String yaml = this.controller.yaml("foo", "bar", false).getBody(); assertEquals("- a: c\n- a: d\n", yaml); } @@ -136,11 +203,12 @@ public class EnvironmentControllerTests { map.put("a.b[0].d", "e"); map.put("a.b[1].c", "d"); this.environment.add(new PropertySource("one", map)); - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); String yaml = this.controller.yaml("foo", "bar", false).getBody(); assertTrue("Wrong output: " + yaml, "a:\n b:\n - d: e\n c: d\n - c: d\n".equals(yaml) - || "a:\n b:\n - c: d\n d: e\n - c: d\n".equals(yaml)); + || "a:\n b:\n - c: d\n d: e\n - c: d\n".equals(yaml)); } @Test @@ -149,7 +217,8 @@ public class EnvironmentControllerTests { map.put("b[0].c", "d"); map.put("b[1].c", "d"); this.environment.add(new PropertySource("one", map)); - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); String yaml = this.controller.yaml("foo", "bar", false).getBody(); assertEquals("b:\n- c: d\n- c: d\n", yaml); } @@ -160,7 +229,8 @@ public class EnvironmentControllerTests { map.put("x.a.b[0].c", "d"); map.put("x.a.b[1].c", "d"); this.environment.add(new PropertySource("one", map)); - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); String yaml = this.controller.yaml("foo", "bar", false).getBody(); assertEquals("x:\n a:\n b:\n - c: d\n - c: d\n", yaml); } @@ -183,51 +253,57 @@ public class EnvironmentControllerTests { Map map = new LinkedHashMap(); map.put("foo", "bar"); this.environment.add(new PropertySource("one", map)); - this.environment.addFirst(new PropertySource("two", Collections.singletonMap("a.b.c", "${foo}"))); - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + this.environment.addFirst( + new PropertySource("two", Collections.singletonMap("a.b.c", "${foo}"))); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); } @Test public void mappingForEnvironment() throws Exception { - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller).build(); - mvc.perform(MockMvcRequestBuilders.get("/foo/bar")).andExpect( - MockMvcResultMatchers.status().isOk()); + mvc.perform(MockMvcRequestBuilders.get("/foo/bar")) + .andExpect(MockMvcResultMatchers.status().isOk()); } @Test public void mappingForLabelledEnvironment() throws Exception { - Mockito.when(this.repository.findOne("foo", "bar", "other")).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", "other")) + .thenReturn(this.environment); MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller).build(); - mvc.perform(MockMvcRequestBuilders.get("/foo/bar/other")).andExpect( - MockMvcResultMatchers.status().isOk()); + mvc.perform(MockMvcRequestBuilders.get("/foo/bar/other")) + .andExpect(MockMvcResultMatchers.status().isOk()); } @Test public void mappingForYaml() throws Exception { - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller).build(); mvc.perform(MockMvcRequestBuilders.get("/foo-bar.yml")) - .andExpect( - MockMvcResultMatchers.content().contentType(MediaType.TEXT_PLAIN)) + .andExpect( + MockMvcResultMatchers.content().contentType(MediaType.TEXT_PLAIN)) .andExpect(MockMvcResultMatchers.content().string("{}\n")); } @Test public void mappingForJson() throws Exception { - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller).build(); mvc.perform(MockMvcRequestBuilders.get("/foo-bar.json")) - .andExpect( - MockMvcResultMatchers.content().contentType( - MediaType.APPLICATION_JSON)) - .andExpect(MockMvcResultMatchers.content().string("{}")); + .andExpect(MockMvcResultMatchers.content() + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.content().string("{}")); ; } @Test public void mappingForLabelledYaml() throws Exception { - Mockito.when(this.repository.findOne("foo", "bar", "other")).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", "other")) + .thenReturn(this.environment); MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller).build(); mvc.perform(MockMvcRequestBuilders.get("/other/foo-bar.yml")).andExpect( MockMvcResultMatchers.content().contentType(MediaType.TEXT_PLAIN)); @@ -235,7 +311,8 @@ public class EnvironmentControllerTests { @Test public void mappingForLabelledProperties() throws Exception { - Mockito.when(this.repository.findOne("foo", "bar", "other")).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", "other")) + .thenReturn(this.environment); MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller).build(); mvc.perform(MockMvcRequestBuilders.get("/other/foo-bar.properties")).andExpect( MockMvcResultMatchers.content().contentType(MediaType.TEXT_PLAIN)); @@ -243,7 +320,8 @@ public class EnvironmentControllerTests { @Test public void mappingForProperties() throws Exception { - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller).build(); mvc.perform(MockMvcRequestBuilders.get("/foo-bar.properties")).andExpect( MockMvcResultMatchers.content().contentType(MediaType.TEXT_PLAIN)); @@ -251,16 +329,18 @@ public class EnvironmentControllerTests { @Test public void mappingForLabelledYamlWithHyphen() throws Exception { - Mockito.when(this.repository.findOne("foo-bar-foo2-bar2", "spam", "other")).thenReturn( - this.environment); + Mockito.when(this.repository.findOne("foo-bar-foo2-bar2", "spam", "other")) + .thenReturn(this.environment); MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller).build(); - mvc.perform(MockMvcRequestBuilders.get("/other/foo-bar-foo2-bar2-spam.yml")).andExpect( - MockMvcResultMatchers.content().contentType(MediaType.TEXT_PLAIN)); + mvc.perform(MockMvcRequestBuilders.get("/other/foo-bar-foo2-bar2-spam.yml")) + .andExpect(MockMvcResultMatchers.content() + .contentType(MediaType.TEXT_PLAIN)); } @Test public void mappingforLabelledJsonProperties() throws Exception { - Mockito.when(this.repository.findOne("foo", "bar", "other")).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", "other")) + .thenReturn(this.environment); MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller).build(); mvc.perform(MockMvcRequestBuilders.get("/other/foo-bar.json")).andExpect( MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)); @@ -268,7 +348,8 @@ public class EnvironmentControllerTests { @Test public void mappingforJsonProperties() throws Exception { - Mockito.when(this.repository.findOne("foo", "bar", null)).thenReturn(this.environment); + Mockito.when(this.repository.findOne("foo", "bar", null)) + .thenReturn(this.environment); MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller).build(); mvc.perform(MockMvcRequestBuilders.get("/foo-bar.json")).andExpect( MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)); @@ -276,11 +357,12 @@ public class EnvironmentControllerTests { @Test public void mappingForLabelledJsonPropertiesWithHyphen() throws Exception { - Mockito.when(this.repository.findOne("foo-bar-foo2-bar2", "spam", "other")).thenReturn( - this.environment); + Mockito.when(this.repository.findOne("foo-bar-foo2-bar2", "spam", "other")) + .thenReturn(this.environment); MockMvc mvc = MockMvcBuilders.standaloneSetup(this.controller).build(); - mvc.perform(MockMvcRequestBuilders.get("/other/foo-bar-foo2-bar2-spam.json")).andExpect( - MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)); + mvc.perform(MockMvcRequestBuilders.get("/other/foo-bar-foo2-bar2-spam.json")) + .andExpect(MockMvcResultMatchers.content() + .contentType(MediaType.APPLICATION_JSON)); }