From 6c802b83ddc1de3392d7b0b8202cea0c92ce89c8 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 1 Oct 2015 09:05:54 +0200 Subject: [PATCH] Stub definitions for dependencies fixes gh-37 --- .../main/asciidoc/spring-cloud-zookeeper.adoc | 23 ++++++ ...pendencyRestTemplateAutoConfiguration.java | 30 +++----- .../dependency/StubsConfiguration.java | 75 +++++++++++++++++++ .../dependency/ZookeeperDependencies.java | 12 ++- .../dependency/ZookeeperDependency.java | 37 +++++++-- .../presence/LogMissingDependencyChecker.java | 8 +- .../dependency/StickyRuleISpec.groovy | 2 + .../dependency/StubsConfigurationSpec.groovy | 50 +++++++++++++ .../ZookeeperDependenciesSpec.groovy | 5 +- ...eeperDiscoveryWithDependenciesISpec.groovy | 9 +++ .../resources/application-dependencies.yml | 1 + 11 files changed, 219 insertions(+), 33 deletions(-) create mode 100644 spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/StubsConfiguration.java create mode 100644 spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/StubsConfigurationSpec.groovy diff --git a/docs/src/main/asciidoc/spring-cloud-zookeeper.adoc b/docs/src/main/asciidoc/spring-cloud-zookeeper.adoc index 6cd5c7e1..26810726 100644 --- a/docs/src/main/asciidoc/spring-cloud-zookeeper.adoc +++ b/docs/src/main/asciidoc/spring-cloud-zookeeper.adoc @@ -112,6 +112,7 @@ spring.cloud.zookeeper: header2: - value2 required: false + stubs: org.springframework:foo:stubs mailing: path: /path/where/mailing/has/registered/in/zookeeper loadBalancerType: ROUND_ROBIN @@ -210,6 +211,28 @@ In other words your application won't be able to start if the required dependenc You can read more about Spring Cloud Zookeeper Presence Checker in the following sections. +=== Stubs + +You can provide a colon separated path to the JAR containing stubs of the dependency. Example + +``` +stubs: org.springframework:foo:stubs +``` + +means that for a particular dependencies can be found under: + +* groupId: `org.springframework` +* artifactId: `foo` +* classifier: `stubs` - this is the default value + +This is actually equal to + +``` +stubs: org.springframework:foo +``` + +since `stubs` is the default classifier. + === Configuring Spring Cloud Zookeeper Dependencies There is a bunch of properties that you can set to enable / disable parts of Zookeeper Dependencies functionalities. diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependencyRestTemplateAutoConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependencyRestTemplateAutoConfiguration.java index 2dd9b844..c93f5932 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependencyRestTemplateAutoConfiguration.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependencyRestTemplateAutoConfiguration.java @@ -18,9 +18,9 @@ package org.springframework.cloud.zookeeper.discovery.dependency; import java.io.IOException; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; @@ -36,11 +36,7 @@ import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.client.RestTemplate; -import com.google.common.base.Function; -import com.google.common.collect.Maps; - /** - * * Customizes RestTemplate to support passing of params from dependency * * @author Marcin Grzejszczak, 4financeIT @@ -79,21 +75,19 @@ public class DependencyRestTemplateAutoConfiguration { } private Map> convertHeadersFromListToCollection(HttpHeaders headers) { - return Maps.transformValues(headers, new Function, Collection>() { - @Override - public Collection apply(List input) { - return input; - } - }); + Map> transformedHeaders = new HashMap<>(); + for (Map.Entry> entry : headers.entrySet()) { + transformedHeaders.put(entry.getKey(), entry.getValue()); + } + return transformedHeaders; } - private Map> convertHeadersFromCollectionToList(Map> headers) { - return Maps.transformValues(headers, new Function, List>() { - @Override - public List apply(Collection input) { - return (List) input; - } - }); + private Map> convertHeadersFromCollectionToList(Map> headers) { + Map> transformedHeaders = new HashMap<>(); + for (Map.Entry> entry : headers.entrySet()) { + transformedHeaders.put(entry.getKey(), (List) entry.getValue()); + } + return transformedHeaders; } }); } diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/StubsConfiguration.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/StubsConfiguration.java new file mode 100644 index 00000000..f22ab772 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/StubsConfiguration.java @@ -0,0 +1,75 @@ +package org.springframework.cloud.zookeeper.discovery.dependency; + +import java.util.Arrays; + +import org.springframework.util.StringUtils; + +import lombok.Data; + +/** + * Representation of a stubs location + * + * @author Marcin Grzejszczak + */ +@Data +public class StubsConfiguration { + private static final String DEFAULT_STUBS_CLASSIFIER = "stubs"; + private static final String STUB_COLON_DELIMITER = ":"; + private static final String PATH_SLASH_DELIMITER = "/"; + + private final String stubsGroupId; + private final String stubsArtifactId; + private final String stubsClassifier; + + public StubsConfiguration(String stubsGroupId, String stubsArtifactId, String stubsClassifier) { + this.stubsGroupId = stubsGroupId; + this.stubsArtifactId = stubsArtifactId; + this.stubsClassifier = StringUtils.hasText(stubsClassifier) ? stubsClassifier : DEFAULT_STUBS_CLASSIFIER; + } + + public StubsConfiguration(String stubPath) { + String[] parsedPath = parsedPathEmptyByDefault(stubPath, STUB_COLON_DELIMITER); + this.stubsGroupId = parsedPath[0]; + this.stubsArtifactId = parsedPath[1]; + this.stubsClassifier = parsedPath[2]; + } + + public StubsConfiguration(DependencyPath path) { + String[] parsedPath = parsedPathEmptyByDefault(path.getPath(), PATH_SLASH_DELIMITER); + this.stubsGroupId = parsedPath[0]; + this.stubsArtifactId = parsedPath[1]; + this.stubsClassifier = parsedPath[2]; + } + + private String[] parsedPathEmptyByDefault(String path, String delimiter) { + String[] splitPath = path.split(delimiter); + String stubsGroupId = ""; + String stubsArtifactId = ""; + String stubsClassifier = ""; + if (splitPath.length >= 2) { + stubsGroupId = splitPath[0]; + stubsArtifactId = splitPath[1]; + stubsClassifier = splitPath.length == 3 ? splitPath[2] : DEFAULT_STUBS_CLASSIFIER; + } + return new String[]{stubsGroupId, stubsArtifactId, stubsClassifier}; + } + + private boolean isDefined() { + return StringUtils.hasText(stubsGroupId) && StringUtils.hasText(stubsArtifactId); + } + + public String toColonSeparatedDependencyNotation() { + if(!isDefined()) { + return ""; + } + return StringUtils.collectionToDelimitedString(Arrays.asList(getStubsGroupId(), getStubsArtifactId(), getStubsClassifier()), STUB_COLON_DELIMITER); + } + + /** + * Marker class to discern between the stubs location and dependency registration path + */ + @Data + static class DependencyPath { + private final String path; + } +} diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependencies.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependencies.java index d7e2930f..4ad9df5d 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependencies.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependencies.java @@ -20,11 +20,11 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; - import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.zookeeper.discovery.dependency.StubsConfiguration.DependencyPath; import org.springframework.util.StringUtils; import lombok.Data; @@ -70,6 +70,16 @@ public class ZookeeperDependencies { if (StringUtils.hasText(prefix)) { value.setPath(prefix + value.getPath()); } + + setStubDefinition(value); + } + } + + private void setStubDefinition(ZookeeperDependency value) { + if (!StringUtils.hasText(value.getStubs())) { + value.setStubsConfiguration(new StubsConfiguration(new DependencyPath(value.getPath()))); + } else { + value.setStubsConfiguration(new StubsConfiguration(value.getStubs())); } } diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependency.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependency.java index 63cb1329..4fb45102 100644 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependency.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependency.java @@ -16,15 +16,14 @@ package org.springframework.cloud.zookeeper.discovery.dependency; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.util.StringUtils; - import java.util.Collection; import java.util.HashMap; import java.util.Map; +import org.springframework.util.StringUtils; + +import lombok.Data; + import static java.util.Collections.singletonList; /** @@ -32,8 +31,6 @@ import static java.util.Collections.singletonList; * @author Spencer Gibb */ @Data -@AllArgsConstructor -@NoArgsConstructor public class ZookeeperDependency { private static final String VERSION_PLACEHOLDER_REGEX = "\\$version"; @@ -77,6 +74,32 @@ public class ZookeeperDependency { */ private boolean required; + /** + * Colon separated notation of the stubs. E.g. {@code org.springframework:zookeeper-sample:stubs}. If not provided + * the {@code path} will be parsed to try to split it into groupId and artifactId. If not provided the classifier + * will by default equal {@code stubs} + */ + private String stubs; + + public ZookeeperDependency() { + } + + public ZookeeperDependency(String path, LoadBalancerType loadBalancerType, String contentTypeTemplate, + String version, Map> headers, boolean required, String stubs) { + this.path = path; + this.loadBalancerType = loadBalancerType; + this.contentTypeTemplate = contentTypeTemplate; + this.version = version; + this.headers = headers; + this.required = required; + this.stubs = stubs; + } + + /** + * Parsed stubs path + */ + private StubsConfiguration stubsConfiguration; + public ZookeeperDependency(String path) { if (StringUtils.hasText(path)) { this.path = path; diff --git a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/presence/LogMissingDependencyChecker.java b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/presence/LogMissingDependencyChecker.java index 26b7d276..00f1c1a2 100755 --- a/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/presence/LogMissingDependencyChecker.java +++ b/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/watcher/presence/LogMissingDependencyChecker.java @@ -15,13 +15,12 @@ */ package org.springframework.cloud.zookeeper.discovery.watcher.presence; -import java.lang.invoke.MethodHandles; import java.util.List; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.curator.x.discovery.ServiceInstance; +import lombok.extern.apachecommons.CommonsLog; + /** * * Will log the missing microservice dependency @@ -29,10 +28,9 @@ import org.apache.curator.x.discovery.ServiceInstance; * @author Marcin Grzejszczak, 4financeIT * @author Tomasz Dziurko, 4financeIT */ +@CommonsLog public class LogMissingDependencyChecker implements PresenceChecker { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - @Override public void checkPresence(String dependencyName, List> serviceInstances) { if (serviceInstances.isEmpty()) { diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/StickyRuleISpec.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/StickyRuleISpec.groovy index e4cfd4ca..f9193561 100644 --- a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/StickyRuleISpec.groovy +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/StickyRuleISpec.groovy @@ -31,6 +31,7 @@ import org.springframework.cloud.zookeeper.discovery.TestServiceRegistrar import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Profile +import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration import org.springframework.util.SocketUtils @@ -41,6 +42,7 @@ import spock.util.environment.RestoreSystemProperties @ContextConfiguration(classes = Config, loader = SpringApplicationContextLoader) @ActiveProfiles('loadbalancerclient') @WebIntegrationTest(randomPort = true) +@DirtiesContext class StickyRuleISpec extends Specification implements PollingUtils { @Autowired LoadBalancerClient loadBalancerClient diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/StubsConfigurationSpec.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/StubsConfigurationSpec.groovy new file mode 100644 index 00000000..e9f8f210 --- /dev/null +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/StubsConfigurationSpec.groovy @@ -0,0 +1,50 @@ +package org.springframework.cloud.zookeeper.discovery.dependency +import spock.lang.Specification +import spock.lang.Unroll + +class StubsConfigurationSpec extends Specification { + + @Unroll + def "should return empty colon separated dependency notation if empty or invalid path [#path] has been provided"() { + when: + StubsConfiguration stubsConfiguration = new StubsConfiguration(path) + then: + '' == stubsConfiguration.toColonSeparatedDependencyNotation() + where: + path << ['', 'pl/'] + } + + def "should properly parse invalid colon separated path into empty notation"() { + given: + String path = 'pl/a' + when: + StubsConfiguration stubsConfiguration = new StubsConfiguration(path) + then: + '' == stubsConfiguration.toColonSeparatedDependencyNotation() + '' == stubsConfiguration.stubsGroupId + '' == stubsConfiguration.stubsArtifactId + '' == stubsConfiguration.stubsClassifier + } + + def "should parse the path into group, artifact and classifier"() { + given: + String path = 'pl/a' + when: + StubsConfiguration stubsConfiguration = new StubsConfiguration(new StubsConfiguration.DependencyPath(path)) + then: + 'pl:a:stubs' == stubsConfiguration.toColonSeparatedDependencyNotation() + 'pl' == stubsConfiguration.stubsGroupId + 'a' == stubsConfiguration.stubsArtifactId + 'stubs' == stubsConfiguration.stubsClassifier + } + + def "should properly set group, artifact and classifier"() { + when: + StubsConfiguration stubsConfiguration = new StubsConfiguration('pl', 'a', 'superstubs') + then: + 'pl:a:superstubs' == stubsConfiguration.toColonSeparatedDependencyNotation() + 'pl' == stubsConfiguration.stubsGroupId + 'a' == stubsConfiguration.stubsArtifactId + 'superstubs' == stubsConfiguration.stubsClassifier + } +} diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependenciesSpec.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependenciesSpec.groovy index 78a5bc26..4328c521 100644 --- a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependenciesSpec.groovy +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDependenciesSpec.groovy @@ -26,8 +26,9 @@ class ZookeeperDependenciesSpec extends Specification { LoadBalancerType.RANDOM, 'contentTypeTemplate', 'version', - [header: 'value'], - false + [header: ['value']], + false, + "" ) private static final Map DEPENDENCIES = [ alias: EXPECTED_DEPENDENCY diff --git a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDiscoveryWithDependenciesISpec.groovy b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDiscoveryWithDependenciesISpec.groovy index 96919ea3..5aa16698 100644 --- a/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDiscoveryWithDependenciesISpec.groovy +++ b/spring-cloud-zookeeper-discovery/src/test/groovy/org/springframework/cloud/zookeeper/discovery/dependency/ZookeeperDiscoveryWithDependenciesISpec.groovy @@ -52,6 +52,15 @@ class ZookeeperDiscoveryWithDependenciesISpec extends Specification implements P } } + def 'should fill out properly the stub section of a dependency'() { + given: + StubsConfiguration stubsConfiguration = zookeeperDependencies.dependencies.get('someAlias').stubsConfiguration + expect: + stubsConfiguration.stubsGroupId == 'org.springframework' + stubsConfiguration.stubsArtifactId == 'foo' + stubsConfiguration.stubsClassifier == 'stubs' + } + def 'should find an instance using feign via serviceID when alias is not found'() { expect: conditions.eventually willPass { diff --git a/spring-cloud-zookeeper-discovery/src/test/resources/application-dependencies.yml b/spring-cloud-zookeeper-discovery/src/test/resources/application-dependencies.yml index 8ce62570..3419b1bd 100644 --- a/spring-cloud-zookeeper-discovery/src/test/resources/application-dependencies.yml +++ b/spring-cloud-zookeeper-discovery/src/test/resources/application-dependencies.yml @@ -12,6 +12,7 @@ spring.cloud.zookeeper: header2: - value2 required: false + stubs: org.springframework:foo:stubs testInstance2: path: somePath2 loadBalancerType: ROUND_ROBIN