diff --git a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySource.java b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySource.java index 4bda748c..4695cfcb 100644 --- a/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySource.java +++ b/spring-cloud-zookeeper-config/src/main/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySource.java @@ -43,7 +43,7 @@ public class ZookeeperPropertySource extends AbstractZookeeperPropertySource { public ZookeeperPropertySource(String context, CuratorFramework source) { super(context, source); - findProperties(this.getContext()); + findProperties(this.getContext(), null); } @Override @@ -56,8 +56,7 @@ public class ZookeeperPropertySource extends AbstractZookeeperPropertySource { byte[] bytes = null; try { bytes = this.getSource().getData().forPath(fullPath); - } - catch (KeeperException e) { + } catch (KeeperException e) { if (e.code() != KeeperException.Code.NONODE) { // not found throw e; } @@ -75,31 +74,30 @@ public class ZookeeperPropertySource extends AbstractZookeeperPropertySource { return strings.toArray(new String[strings.size()]); } - private void findProperties(String path) { + private void findProperties(String path, List children) { try { log.trace("entering findProperties for path: " + path); - List children = null; - try { - children = this.getSource().getChildren().forPath(path); - } - catch (KeeperException e) { - if (e.code() != KeeperException.Code.NONODE) { // not found - throw e; - } + if (children == null) { + children = getChildren(path); } if (children == null || children.isEmpty()) { return; } for (String child : children) { String childPath = path + "/" + child; + List childPathChildren = getChildren(childPath); + byte[] bytes = getPropertyBytes(childPath); if (bytes == null || bytes.length == 0) { - findProperties(childPath); - } - else { - String key = sanitizeKey(childPath); - this.properties.put(key, new String(bytes, Charset.forName("UTF-8"))); + if (childPathChildren == null || childPathChildren.isEmpty()) { + registerKeyValue(childPath, ""); + } + } else { + registerKeyValue(childPath, new String(bytes, Charset.forName("UTF-8"))); } + + // Check children even if we have found a value for the current znode + findProperties(childPath, childPathChildren); } log.trace("leaving findProperties for path: " + path); } catch (Exception exception) { @@ -107,4 +105,21 @@ public class ZookeeperPropertySource extends AbstractZookeeperPropertySource { } } + private void registerKeyValue(String path, String value) { + String key = sanitizeKey(path); + this.properties.put(key, value); + } + + private List getChildren(String path) throws Exception { + List children = null; + try { + children = this.getSource().getChildren().forPath(path); + } catch (KeeperException e) { + if (e.code() != KeeperException.Code.NONODE) { // not found + throw e; + } + } + return children; + } + } diff --git a/spring-cloud-zookeeper-config/src/test/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySourceLocatorTests.java b/spring-cloud-zookeeper-config/src/test/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySourceLocatorTests.java index 4f73fe24..0e8dbdec 100644 --- a/spring-cloud-zookeeper-config/src/test/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySourceLocatorTests.java +++ b/spring-cloud-zookeeper-config/src/test/java/org/springframework/cloud/zookeeper/config/ZookeeperPropertySourceLocatorTests.java @@ -34,7 +34,6 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.cloud.context.scope.refresh.RefreshScope; import org.springframework.cloud.endpoint.RefreshEndpoint; -import org.springframework.cloud.zookeeper.ZookeeperProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -43,6 +42,7 @@ import org.springframework.util.SocketUtils; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyString; import static org.junit.Assert.assertThat; /** @@ -54,7 +54,23 @@ public class ZookeeperPropertySourceLocatorTests { public static final String PREFIX = "test__config__"; public static final String ROOT = "/" + PREFIX + UUID.randomUUID(); private ConfigurableApplicationContext context; - public static final String KEY = ROOT + "/application/testProp"; + public static final String CONTEXT = ROOT + "/application/"; + + public static final String KEY_BASIC = "testProp"; + public static final String KEY_BASIC_PATH = CONTEXT + KEY_BASIC; + public static final String VAL_BASIC = "testPropVal"; + + public static final String KEY_WITH_DOT = "testProp.dot"; + public static final String KEY_WITH_DOT_PATH = CONTEXT + KEY_WITH_DOT; + public static final String VAL_WITH_DOT = "withDotVal"; + + public static final String KEY_NESTED = "testProp.nested"; + public static final String KEY_NESTED_PATH = CONTEXT + KEY_NESTED.replace('.','/'); + public static final String VAL_NESTED = "nestedVal"; + + public static final String KEY_WITHOUT_VALUE = "testProp.novalue"; + public static final String KEY_WITHOUT_VALUE_PATH = CONTEXT + KEY_WITHOUT_VALUE; + private TestingServer testingServer; @Configuration @@ -110,7 +126,18 @@ public class ZookeeperPropertySourceLocatorTests { } } - String create = this.curator.create().creatingParentsIfNeeded().forPath(KEY, "testPropVal".getBytes()); + StringBuilder create = new StringBuilder(1024); + create.append( + this.curator.create().creatingParentsIfNeeded().forPath(KEY_BASIC_PATH, VAL_BASIC.getBytes())).append( + '\n'); + create.append(this.curator.create().creatingParentsIfNeeded().forPath(KEY_WITH_DOT_PATH, + VAL_WITH_DOT.getBytes())).append('\n'); + create.append( + this.curator.create().creatingParentsIfNeeded().forPath(KEY_NESTED_PATH, VAL_NESTED.getBytes())) + .append( + '\n'); + create.append(this.curator.create().creatingParentsIfNeeded().forPath(KEY_WITHOUT_VALUE_PATH, null)).append( + '\n'); this.curator.close(); System.out.println(create); @@ -145,17 +172,32 @@ public class ZookeeperPropertySourceLocatorTests { } @Test - public void propertyLoadedAndUpdated() throws Exception { - String testProp = this.environment.getProperty("testProp"); - assertThat("testProp was wrong", testProp, is(equalTo("testPropVal"))); + public void checkKeyValues() throws Exception { + String propValue = this.environment.getProperty(KEY_BASIC); + assertThat(KEY_BASIC + " was wrong", propValue, is(equalTo(VAL_BASIC))); - this.curator.setData().forPath(KEY, "testPropValUpdate".getBytes()); + propValue = this.environment.getProperty(KEY_NESTED); + assertThat(KEY_NESTED + " was wrong", propValue, is(equalTo(VAL_NESTED))); + + propValue = this.environment.getProperty(KEY_WITH_DOT); + assertThat(KEY_BASIC + " was wrong", propValue, is(equalTo(VAL_WITH_DOT))); + + propValue = this.environment.getProperty(KEY_WITHOUT_VALUE); + assertThat(KEY_BASIC + " was wrong", propValue, is(isEmptyString())); + } + + @Test + public void propertyLoadedAndUpdated() throws Exception { + String testProp = this.environment.getProperty(KEY_BASIC); + assertThat("testProp was wrong", testProp, is(equalTo(VAL_BASIC))); + + this.curator.setData().forPath(KEY_BASIC_PATH, "testPropValUpdate".getBytes()); CountDownLatch latch = this.context.getBean(CountDownLatch.class); boolean receivedEvent = latch.await(15, TimeUnit.SECONDS); assertThat("listener didn't receive event", receivedEvent, is(true)); - testProp = this.environment.getProperty("testProp"); + testProp = this.environment.getProperty(KEY_BASIC); assertThat("testProp was wrong after update", testProp, is(equalTo("testPropValUpdate"))); } }