Adds support for paths in ConfigData
Now `zookeeper:myhost:8501/mypath1;/mypath2;/mypath3` will use the paths separated by semicolon (;) instead of automatically generating paths. `zookeeper:myhost:8501` or `zookeeper:` will automatically generate paths from ZookeeperConfigProperties and active profiles.
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.zookeeper.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -36,19 +37,28 @@ import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.cloud.zookeeper.ZookeeperProperties;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
public class ZookeeperConfigDataLocationResolver implements ConfigDataLocationResolver<ZookeeperConfigDataLocation> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ZookeeperConfigDataLocationResolver.class);
|
||||
|
||||
/**
|
||||
* Zookeeper Config Data prefix.
|
||||
*/
|
||||
public static final String PREFIX = "zookeeper:";
|
||||
|
||||
@Override
|
||||
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) {
|
||||
boolean zkEnabled = context.getBinder().bind(ZookeeperProperties.PREFIX + ".enabled", Boolean.class)
|
||||
.orElse(true);
|
||||
boolean zkConfigEnabled = context.getBinder().bind(ZookeeperConfigProperties.PREFIX + ".enabled", Boolean.class)
|
||||
.orElse(true);
|
||||
return location.startsWith("zookeeper:") && zkConfigEnabled && zkEnabled;
|
||||
return location.startsWith(PREFIX) && zkConfigEnabled && zkEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,29 +70,16 @@ public class ZookeeperConfigDataLocationResolver implements ConfigDataLocationRe
|
||||
@Override
|
||||
public List<ZookeeperConfigDataLocation> resolveProfileSpecific(ConfigDataLocationResolverContext context,
|
||||
String location, boolean optional, Profiles profiles) throws ConfigDataLocationNotFoundException {
|
||||
|
||||
String appName = context.getBinder().bind("spring.application.name", String.class).orElse("application");
|
||||
UriComponents locationUri = parseLocation(context, location);
|
||||
|
||||
ZookeeperConfigProperties properties = loadConfigProperties(context.getBinder());
|
||||
String root = properties.getRoot();
|
||||
List<String> contexts = new ArrayList<>();
|
||||
|
||||
String defaultContext = root + "/" + properties.getDefaultContext();
|
||||
contexts.add(defaultContext);
|
||||
addProfiles(contexts, defaultContext, profiles, properties);
|
||||
|
||||
StringBuilder baseContext = new StringBuilder(root);
|
||||
if (!appName.startsWith("/")) {
|
||||
baseContext.append("/");
|
||||
}
|
||||
baseContext.append(appName);
|
||||
contexts.add(baseContext.toString());
|
||||
addProfiles(contexts, baseContext.toString(), profiles, properties);
|
||||
|
||||
Collections.reverse(contexts);
|
||||
List<String> contexts = (locationUri == null || CollectionUtils.isEmpty(locationUri.getPathSegments()))
|
||||
? getAutomaticContexts(profiles, properties) : getCustomContexts(locationUri, properties);
|
||||
|
||||
context.getBootstrapRegistry()
|
||||
.register(CuratorFramework.class, () -> curatorFramework(optional, loadProperties(context, location)))
|
||||
.register(CuratorFramework.class,
|
||||
() -> curatorFramework(optional, loadProperties(context.getBinder(), locationUri)))
|
||||
.onApplicationContextPrepared((ctxt, curatorFramework) -> {
|
||||
ctxt.getBeanFactory().registerSingleton("configDataCuratorFramework", curatorFramework);
|
||||
HashMap<String, Object> source = new HashMap<>();
|
||||
@@ -98,6 +95,50 @@ public class ZookeeperConfigDataLocationResolver implements ConfigDataLocationRe
|
||||
return locations;
|
||||
}
|
||||
|
||||
protected List<String> getCustomContexts(UriComponents uriComponents, ZookeeperConfigProperties properties) {
|
||||
if (StringUtils.isEmpty(uriComponents.getPath())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Arrays.asList(uriComponents.getPath().split(";"));
|
||||
}
|
||||
|
||||
protected List<String> getAutomaticContexts(Profiles profiles, ZookeeperConfigProperties properties) {
|
||||
String root = properties.getRoot();
|
||||
List<String> contexts = new ArrayList<>();
|
||||
|
||||
String defaultContext = root + "/" + properties.getDefaultContext();
|
||||
contexts.add(defaultContext);
|
||||
addProfiles(contexts, defaultContext, profiles, properties);
|
||||
|
||||
StringBuilder baseContext = new StringBuilder(root);
|
||||
if (!properties.getName().startsWith("/")) {
|
||||
baseContext.append("/");
|
||||
}
|
||||
// getName() defaults to ${spring.application.name} or application
|
||||
baseContext.append(properties.getName());
|
||||
contexts.add(baseContext.toString());
|
||||
addProfiles(contexts, baseContext.toString(), profiles, properties);
|
||||
|
||||
Collections.reverse(contexts);
|
||||
return contexts;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected UriComponents parseLocation(ConfigDataLocationResolverContext context, String location) {
|
||||
String uri = location.substring(PREFIX.length());
|
||||
if (!StringUtils.hasText(uri)) {
|
||||
return null;
|
||||
}
|
||||
if (!uri.startsWith("//")) {
|
||||
uri = PREFIX + "//" + uri;
|
||||
}
|
||||
else {
|
||||
uri = location;
|
||||
}
|
||||
return UriComponentsBuilder.fromUriString(uri).build();
|
||||
}
|
||||
|
||||
private void addProfiles(List<String> contexts, String baseContext, Profiles profiles,
|
||||
ZookeeperConfigProperties properties) {
|
||||
for (String profile : profiles.getAccepted()) {
|
||||
@@ -144,18 +185,17 @@ public class ZookeeperConfigDataLocationResolver implements ConfigDataLocationRe
|
||||
properties.getMaxSleepMs());
|
||||
}
|
||||
|
||||
private ZookeeperProperties loadProperties(ConfigDataLocationResolverContext context, String location) {
|
||||
ZookeeperProperties properties = context.getBinder()
|
||||
.bind(ZookeeperProperties.PREFIX, Bindable.of(ZookeeperProperties.class))
|
||||
protected ZookeeperProperties loadProperties(Binder binder, UriComponents location) {
|
||||
ZookeeperProperties properties = binder.bind(ZookeeperProperties.PREFIX, Bindable.of(ZookeeperProperties.class))
|
||||
.orElse(new ZookeeperProperties());
|
||||
|
||||
String connectString = location.substring("zookeeper:".length());
|
||||
if (StringUtils.hasText(connectString)) {
|
||||
properties.setConnectString(connectString);
|
||||
if (location != null && StringUtils.hasText(location.getHost())) {
|
||||
if (location.getPort() < 0) {
|
||||
throw new IllegalArgumentException("zookeeper port must be greater than or equal to zero: " + location.getPort());
|
||||
}
|
||||
properties.setConnectString(location.getHost() + ":" + location.getPort());
|
||||
}
|
||||
|
||||
// TODO: support ZookeeperConfigProperties.root
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -163,6 +203,11 @@ public class ZookeeperConfigDataLocationResolver implements ConfigDataLocationRe
|
||||
ZookeeperConfigProperties properties = binder
|
||||
.bind(ZookeeperConfigProperties.PREFIX, Bindable.of(ZookeeperConfigProperties.class))
|
||||
.orElse(new ZookeeperConfigProperties());
|
||||
|
||||
if (StringUtils.isEmpty(properties.getName())) {
|
||||
properties.setName(binder.bind("spring.application.name", String.class).orElse("application"));
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.zookeeper.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -41,6 +42,11 @@ public class ZookeeperConfigProperties {
|
||||
*/
|
||||
private String root = "config";
|
||||
|
||||
/**
|
||||
* Alternative to spring.application.name to use in looking up values in zookeeper.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The name of the default context.
|
||||
*/
|
||||
@@ -60,42 +66,62 @@ public class ZookeeperConfigProperties {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public String getRoot() {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
public String getDefaultContext() {
|
||||
return this.defaultContext;
|
||||
}
|
||||
|
||||
public String getProfileSeparator() {
|
||||
return this.profileSeparator;
|
||||
}
|
||||
|
||||
public boolean isFailFast() {
|
||||
return this.failFast;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getRoot() {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
public void setRoot(String root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDefaultContext() {
|
||||
return this.defaultContext;
|
||||
}
|
||||
|
||||
public void setDefaultContext(String defaultContext) {
|
||||
Assert.hasText(defaultContext, "spring.cloud.zookeeper.config.default-context may not be empty");
|
||||
this.defaultContext = defaultContext;
|
||||
}
|
||||
|
||||
public String getProfileSeparator() {
|
||||
return this.profileSeparator;
|
||||
}
|
||||
|
||||
public void setProfileSeparator(String profileSeparator) {
|
||||
Assert.hasText(profileSeparator, "spring.cloud.zookeeper.config.profile-separator may not be empty");
|
||||
this.profileSeparator = profileSeparator;
|
||||
}
|
||||
|
||||
public boolean isFailFast() {
|
||||
return this.failFast;
|
||||
}
|
||||
|
||||
public void setFailFast(boolean failFast) {
|
||||
this.failFast = failFast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this)
|
||||
.append("enabled", enabled)
|
||||
.append("root", root)
|
||||
.append("name", name)
|
||||
.append("defaultContext", defaultContext)
|
||||
.append("profileSeparator", profileSeparator)
|
||||
.append("failFast", failFast)
|
||||
.toString();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Zookeeper provides a <a href=
|
||||
@@ -88,12 +89,13 @@ public class ZookeeperPropertySourceLocator implements PropertySourceLocator {
|
||||
public PropertySource<?> locate(Environment environment) {
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
|
||||
String appName = env.getProperty("spring.application.name");
|
||||
if (appName == null) {
|
||||
String appName = properties.getName();
|
||||
if (StringUtils.isEmpty(appName)) {
|
||||
// use default "application" (which config client does)
|
||||
appName = "application";
|
||||
log.warn(
|
||||
"spring.application.name is not set. Using default of 'application'");
|
||||
appName = env.getProperty("spring.application.name", "application");
|
||||
if (appName.equals("application")) {
|
||||
log.warn("spring.application.name is not set. Using default of 'application'");
|
||||
}
|
||||
}
|
||||
List<String> profiles = Arrays.asList(env.getActiveProfiles());
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
|
||||
import org.springframework.boot.context.config.Profiles;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.env.BootstrapRegistry;
|
||||
import org.springframework.cloud.zookeeper.ZookeeperProperties;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ZookeeperConfigDataLocationResolverTests {
|
||||
|
||||
@Test
|
||||
public void testParseLocation() {
|
||||
ZookeeperConfigDataLocationResolver resolver = new ZookeeperConfigDataLocationResolver();
|
||||
UriComponents uriComponents = resolver.parseLocation(null,
|
||||
"zookeeper:myhost:2182/mypath1;/mypath2;/mypath3");
|
||||
assertThat(uriComponents.toUri()).hasScheme("zookeeper").hasHost("myhost")
|
||||
.hasPort(2182).hasPath("/mypath1;/mypath2;/mypath3");
|
||||
|
||||
uriComponents = resolver.parseLocation(null, "zookeeper:myhost:2182");
|
||||
assertThat(uriComponents.toUri()).hasScheme("zookeeper").hasHost("myhost")
|
||||
.hasPort(2182).hasPath("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveProfileSpecificWithCustomPaths() {
|
||||
String location = "zookeeper:myhost:2182/mypath1;/mypath2;/mypath3";
|
||||
List<ZookeeperConfigDataLocation> locations = testResolveProfileSpecific(location);
|
||||
assertThat(locations).hasSize(3);
|
||||
assertThat(toContexts(locations)).containsExactly("/mypath1", "/mypath2",
|
||||
"/mypath3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveProfileSpecificWithAutomaticPaths() {
|
||||
String location = "zookeeper:myhost";
|
||||
List<ZookeeperConfigDataLocation> locations = testResolveProfileSpecific(location);
|
||||
assertThat(locations).hasSize(4);
|
||||
assertThat(toContexts(locations)).containsExactly("config/testapp,dev",
|
||||
"config/testapp", "config/application,dev", "config/application");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadProperties() {
|
||||
ZookeeperProperties properties = createResolver().loadProperties(
|
||||
Binder.get(new MockEnvironment()),
|
||||
UriComponentsBuilder.fromUriString("zookeeper://myhost:8502").build());
|
||||
assertThat(properties.getConnectString()).isEqualTo("myhost:8502");
|
||||
}
|
||||
|
||||
private List<String> toContexts(List<ZookeeperConfigDataLocation> locations) {
|
||||
return locations.stream().map(ZookeeperConfigDataLocation::getContext)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<ZookeeperConfigDataLocation> testResolveProfileSpecific(String location) {
|
||||
ZookeeperConfigDataLocationResolver resolver = createResolver();
|
||||
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setProperty("spring.application.name", "testapp");
|
||||
|
||||
BootstrapRegistry registry = mock(BootstrapRegistry.class);
|
||||
when(registry.register(any(), any())).thenReturn(mock(BootstrapRegistry.Registration.class));
|
||||
|
||||
ConfigDataLocationResolverContext context = mock(
|
||||
ConfigDataLocationResolverContext.class);
|
||||
|
||||
when(context.getBootstrapRegistry()).thenReturn(registry);
|
||||
when(context.getBinder()).thenReturn(Binder.get(env));
|
||||
|
||||
Profiles profiles = mock(Profiles.class);
|
||||
when(profiles.getAccepted()).thenReturn(Collections.singletonList("dev"));
|
||||
|
||||
return resolver.resolveProfileSpecific(context, location, false, profiles);
|
||||
}
|
||||
|
||||
private ZookeeperConfigDataLocationResolver createResolver() {
|
||||
return new ZookeeperConfigDataLocationResolver();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user