Fixes ordering with local and remote sources with profiles.
If the context is associated with a profile, the Option.PROFILE_SPECIFIC is added. Fixes gh-287
This commit is contained in:
@@ -16,16 +16,28 @@
|
||||
|
||||
package org.springframework.cloud.zookeeper.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
|
||||
import org.springframework.boot.context.config.ConfigData;
|
||||
import org.springframework.boot.context.config.ConfigDataLoader;
|
||||
import org.springframework.boot.context.config.ConfigDataLoaderContext;
|
||||
import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class ZookeeperConfigDataLoader implements ConfigDataLoader<ZookeeperConfigDataResource> {
|
||||
private static final EnumSet<ConfigData.Option> ALL_OPTIONS = EnumSet.allOf(ConfigData.Option.class);
|
||||
|
||||
private final Log log;
|
||||
|
||||
public ZookeeperConfigDataLoader(Log log) {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigData load(ConfigDataLoaderContext context, ZookeeperConfigDataResource resource) {
|
||||
@@ -33,11 +45,36 @@ public class ZookeeperConfigDataLoader implements ConfigDataLoader<ZookeeperConf
|
||||
CuratorFramework curator = context.getBootstrapContext().get(CuratorFramework.class);
|
||||
ZookeeperPropertySource propertySource = new ZookeeperPropertySource(resource.getContext(),
|
||||
curator);
|
||||
return new ConfigData(Collections.singletonList(propertySource));
|
||||
List<ZookeeperPropertySource> propertySources = Collections.singletonList(propertySource);
|
||||
if (ALL_OPTIONS.size() == 1) {
|
||||
// boot 2.4.2 and prior
|
||||
return new ConfigData(propertySources);
|
||||
}
|
||||
else if (ALL_OPTIONS.size() == 2) {
|
||||
// boot 2.4.3 and 2.4.4
|
||||
return new ConfigData(propertySources, ConfigData.Option.IGNORE_IMPORTS, ConfigData.Option.IGNORE_PROFILES);
|
||||
}
|
||||
else if (ALL_OPTIONS.size() > 2) {
|
||||
// boot 2.4.5+
|
||||
return new ConfigData(propertySources, source -> {
|
||||
List<ConfigData.Option> options = new ArrayList<>();
|
||||
options.add(ConfigData.Option.IGNORE_IMPORTS);
|
||||
options.add(ConfigData.Option.IGNORE_PROFILES);
|
||||
if (StringUtils.hasText(resource.getProfile())) {
|
||||
options.add(ConfigData.Option.PROFILE_SPECIFIC);
|
||||
}
|
||||
return ConfigData.Options.of(options.toArray(new ConfigData.Option[0]));
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Error getting properties from consul: " + resource, e);
|
||||
}
|
||||
throw new ConfigDataResourceNotFoundException(resource, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
@@ -35,6 +36,7 @@ import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.cloud.zookeeper.CuratorFactory;
|
||||
import org.springframework.cloud.zookeeper.ZookeeperProperties;
|
||||
import org.springframework.cloud.zookeeper.config.ZookeeperPropertySources.Context;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -88,20 +90,21 @@ public class ZookeeperConfigDataLocationResolver implements ConfigDataLocationRe
|
||||
|
||||
ZookeeperPropertySources sources = new ZookeeperPropertySources(properties, log);
|
||||
|
||||
List<String> contexts = (locationUri == null || CollectionUtils.isEmpty(locationUri.getPathSegments()))
|
||||
? sources.getAutomaticContexts(profiles.getAccepted(), false) : getCustomContexts(locationUri);
|
||||
List<Context> contexts = (locationUri == null || CollectionUtils.isEmpty(locationUri.getPathSegments()))
|
||||
? sources.generateAutomaticContexts(profiles.getAccepted(), false) : getCustomContexts(locationUri);
|
||||
|
||||
// promote beans to context
|
||||
context.getBootstrapContext().addCloseListener(event -> {
|
||||
HashMap<String, Object> source = new HashMap<>();
|
||||
source.put("spring.cloud.zookeeper.config.property-source-contexts", contexts);
|
||||
source.put("spring.cloud.zookeeper.config.property-source-contexts", contexts.stream().map(Context::getPath).collect(Collectors.toList()));
|
||||
MapPropertySource propertySource = new MapPropertySource("zookeeperConfigData", source);
|
||||
event.getApplicationContext().getEnvironment().getPropertySources().addFirst(propertySource);
|
||||
});
|
||||
|
||||
ArrayList<ZookeeperConfigDataResource> locations = new ArrayList<>();
|
||||
contexts.forEach(propertySourceContext -> locations
|
||||
.add(new ZookeeperConfigDataResource(propertySourceContext, location.isOptional())));
|
||||
.add(new ZookeeperConfigDataResource(propertySourceContext.getPath(), location.isOptional(), propertySourceContext
|
||||
.getProfile())));
|
||||
|
||||
return locations;
|
||||
}
|
||||
@@ -110,12 +113,12 @@ public class ZookeeperConfigDataLocationResolver implements ConfigDataLocationRe
|
||||
return context.getBootstrapContext().getOrElse(BindHandler.class, null);
|
||||
}
|
||||
|
||||
protected List<String> getCustomContexts(UriComponents uriComponents) {
|
||||
protected List<Context> getCustomContexts(UriComponents uriComponents) {
|
||||
if (!StringUtils.hasLength(uriComponents.getPath())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Arrays.asList(uriComponents.getPath().split(";"));
|
||||
return Arrays.stream(uriComponents.getPath().split(";")).map(Context::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -25,10 +25,17 @@ public class ZookeeperConfigDataResource extends ConfigDataResource {
|
||||
|
||||
private final String context;
|
||||
private final boolean optional;
|
||||
private final String profile;
|
||||
|
||||
public ZookeeperConfigDataResource(String context, boolean optional) {
|
||||
public ZookeeperConfigDataResource(String context, boolean optional, String profile) {
|
||||
this.context = context;
|
||||
this.optional = optional;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ZookeeperConfigDataResource(String context, boolean optional) {
|
||||
this(context, optional, null);
|
||||
}
|
||||
|
||||
public String getContext() {
|
||||
@@ -39,6 +46,10 @@ public class ZookeeperConfigDataResource extends ConfigDataResource {
|
||||
return this.optional;
|
||||
}
|
||||
|
||||
public String getProfile() {
|
||||
return this.profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -48,13 +59,12 @@ public class ZookeeperConfigDataResource extends ConfigDataResource {
|
||||
return false;
|
||||
}
|
||||
ZookeeperConfigDataResource that = (ZookeeperConfigDataResource) o;
|
||||
return this.optional == that.optional &&
|
||||
this.context.equals(that.context);
|
||||
return this.optional == that.optional && this.context.equals(that.context) && Objects.equals(this.profile, that.profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.optional, this.context);
|
||||
return Objects.hash(this.optional, this.context, this.profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,6 +72,7 @@ public class ZookeeperConfigDataResource extends ConfigDataResource {
|
||||
return new ToStringCreator(this)
|
||||
.append("context", context)
|
||||
.append("optional", optional)
|
||||
.append("profile", profile)
|
||||
.toString();
|
||||
|
||||
}
|
||||
|
||||
@@ -19,10 +19,13 @@ package org.springframework.cloud.zookeeper.config;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
public class ZookeeperPropertySources {
|
||||
private final ZookeeperConfigProperties properties;
|
||||
private final Log log;
|
||||
@@ -37,11 +40,15 @@ public class ZookeeperPropertySources {
|
||||
}
|
||||
|
||||
public List<String> getAutomaticContexts(List<String> profiles, boolean reverse) {
|
||||
return generateAutomaticContexts(profiles, reverse).stream().map(Context::getPath).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Context> generateAutomaticContexts(List<String> profiles, boolean reverse) {
|
||||
List<Context> contexts = new ArrayList<>();
|
||||
String root = properties.getRoot();
|
||||
List<String> contexts = new ArrayList<>();
|
||||
|
||||
String defaultContext = root + "/" + properties.getDefaultContext();
|
||||
contexts.add(defaultContext);
|
||||
contexts.add(new Context(defaultContext));
|
||||
addProfiles(contexts, defaultContext, profiles);
|
||||
|
||||
StringBuilder baseContext = new StringBuilder(root);
|
||||
@@ -50,7 +57,7 @@ public class ZookeeperPropertySources {
|
||||
}
|
||||
// getName() defaults to ${spring.application.name} or application
|
||||
baseContext.append(properties.getName());
|
||||
contexts.add(baseContext.toString());
|
||||
contexts.add(new Context(baseContext.toString()));
|
||||
addProfiles(contexts, baseContext.toString(), profiles);
|
||||
|
||||
if (reverse) {
|
||||
@@ -59,9 +66,10 @@ public class ZookeeperPropertySources {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private void addProfiles(List<String> contexts, String baseContext, List<String> profiles) {
|
||||
private void addProfiles(List<Context> contexts, String baseContext, List<String> profiles) {
|
||||
for (String profile : profiles) {
|
||||
contexts.add(baseContext + properties.getProfileSeparator() + profile);
|
||||
String path = baseContext + properties.getProfileSeparator() + profile;
|
||||
contexts.add(new Context(path, profile));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +88,38 @@ public class ZookeeperPropertySources {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static class Context {
|
||||
|
||||
private final String path;
|
||||
|
||||
private final String profile;
|
||||
|
||||
public Context(String path) {
|
||||
this.path = path;
|
||||
this.profile = null;
|
||||
}
|
||||
|
||||
public Context(String path, String profile) {
|
||||
this.path = path;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public String getProfile() {
|
||||
return this.profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("path", path).append("profile", profile).toString();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static class ZookeeperPropertySourceNotFoundException extends RuntimeException {
|
||||
|
||||
|
||||
@@ -67,6 +67,13 @@
|
||||
<artifactId>curator-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-zookeeper-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2018-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.zookeeper.sample;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.framework.imps.CuratorFrameworkState;
|
||||
import org.apache.curator.retry.RetryOneTime;
|
||||
import org.apache.zookeeper.KeeperException;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.cloud.zookeeper.ZookeeperProperties;
|
||||
import org.springframework.cloud.zookeeper.config.ZookeeperConfigProperties;
|
||||
import org.springframework.cloud.zookeeper.test.ZookeeperTestingServer;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
@SpringBootTest(classes = SampleZookeeperApplication.class,
|
||||
properties = { "spring.application.name=" + ZookeeperConfigDataOrderingIntegrationTests.APP_NAME,
|
||||
"spring.config.name=orderingtest", "spring.profiles.active=dev",
|
||||
"management.endpoints.web.exposure.include=*" },
|
||||
webEnvironment = RANDOM_PORT)
|
||||
public class ZookeeperConfigDataOrderingIntegrationTests {
|
||||
|
||||
private static final String BASE_PATH = new WebEndpointProperties().getBasePath();
|
||||
|
||||
static final String APP_NAME = "testZkConfigDataOrderingIntegration";
|
||||
|
||||
private static final String PREFIX = "_configDataOrderingIntegrationTests_config__";
|
||||
|
||||
private static final String ROOT = "/" + PREFIX + UUID.randomUUID();
|
||||
|
||||
private static final String VALUE = "my value from zk default profile";
|
||||
|
||||
private static final String TEST_PROP = "my.prop";
|
||||
|
||||
private static final String KEY = ROOT + "/" + APP_NAME + "/" + TEST_PROP;
|
||||
|
||||
private static final String VALUE_PROFILE = "my value from zk dev profile";
|
||||
|
||||
private static final String KEY_PROFILE = ROOT + "/" + APP_NAME + ",dev/" + TEST_PROP;
|
||||
private static ZookeeperTestingServer testingServer;
|
||||
private static CuratorFramework curator;
|
||||
|
||||
@Autowired
|
||||
private Environment env;
|
||||
|
||||
@BeforeAll
|
||||
public static void initialize() throws Exception {
|
||||
testingServer = new ZookeeperTestingServer();
|
||||
testingServer.start();
|
||||
System.setProperty(ZookeeperProperties.PREFIX + ".connect-string", "localhost:" + testingServer.getPort());
|
||||
System.setProperty(ZookeeperConfigProperties.PREFIX + ".root", ROOT);
|
||||
String connectString = "localhost:" + testingServer.getPort();
|
||||
curator = CuratorFrameworkFactory.builder()
|
||||
.retryPolicy(new RetryOneTime(500)).connectString(connectString).build();
|
||||
curator.start();
|
||||
List<String> children = curator.getChildren().forPath("/");
|
||||
for (String child : children) {
|
||||
if (child.startsWith(PREFIX) && child.length() > PREFIX.length()) {
|
||||
delete("/" + child);
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder create = new StringBuilder(1024);
|
||||
create.append(curator.create().creatingParentsIfNeeded()
|
||||
.forPath(KEY, VALUE.getBytes())).append('\n');
|
||||
create.append(curator.create().creatingParentsIfNeeded()
|
||||
.forPath(KEY_PROFILE, VALUE_PROFILE.getBytes())).append('\n');
|
||||
curator.close();
|
||||
System.out.println(create);
|
||||
}
|
||||
|
||||
public static void delete(String path) throws Exception {
|
||||
try {
|
||||
if (curator.getState() == CuratorFrameworkState.STARTED) {
|
||||
curator.delete().deletingChildrenIfNeeded().forPath(path);
|
||||
}
|
||||
}
|
||||
catch (KeeperException e) {
|
||||
if (e.code() != KeeperException.Code.NONODE) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@AfterAll
|
||||
public static void close() throws Exception {
|
||||
try {
|
||||
delete(ROOT);
|
||||
}
|
||||
finally {
|
||||
testingServer.close();
|
||||
}
|
||||
System.clearProperty(ZookeeperProperties.PREFIX + ".connect-string");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void contextLoads() {
|
||||
Integer port = env.getProperty("local.server.port", Integer.class);
|
||||
ResponseEntity<Map> response = new TestRestTemplate()
|
||||
.getForEntity("http://localhost:" + port + BASE_PATH + "/env/my.prop", Map.class);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
Map res = response.getBody();
|
||||
assertThat(res).containsKey("propertySources");
|
||||
Map<String, Object> property = (Map<String, Object>) res.get("property");
|
||||
assertThat(property).containsEntry("value", VALUE_PROFILE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
my.prop=my value from local dev profile
|
||||
@@ -0,0 +1 @@
|
||||
spring.config.import=zookeeper:
|
||||
Reference in New Issue
Block a user