From c4721e9510fb5d268485a790464f61a1f0a9be1e Mon Sep 17 00:00:00 2001 From: Ben Hale Date: Mon, 11 May 2020 11:26:16 -0700 Subject: [PATCH] Flattened Bindings Another common way to use bindings, even without auto-configuration is to refer to them in a flattened form within other properties. An example would be `spring.datasource.password=cnb.bindings.my-db.secret.password`. This change adds an EnvironmentPostProcessor that contributes these flattened bindings in a PropertySource. [resolves #24] Signed-off-by: Ben Hale --- pom.xml | 8 +- .../cloud/bindings/Binding.java | 26 +++-- .../cloud/bindings/Bindings.java | 44 ++++----- ...dingFlattenedEnvironmentPostProcessor.java | 84 +++++++++++++++++ ...dingSpecificEnvironmentPostProcessor.java} | 41 +++----- .../boot/BindingsPropertiesProcessor.java | 3 +- .../CassandraBindingsPropertiesProcessor.java | 4 +- .../boot/Db2BindingsPropertiesProcessor.java | 4 +- .../cloud/bindings/boot/Guards.java | 4 +- .../MongoDbBindingsPropertiesProcessor.java | 3 +- .../MySqlBindingsPropertiesProcessor.java | 4 +- .../OracleBindingsPropertiesProcessor.java | 4 +- ...PostgreSqlBindingsPropertiesProcessor.java | 4 +- .../boot/PropertySourceContributor.java | 40 ++++++++ .../RedisBindingsPropertiesProcessor.java | 4 +- .../SqlServerBindingsPropertiesProcessor.java | 4 +- .../cloud/bindings/boot/package-info.java | 20 ++++ .../cloud/bindings/package-info.java | 20 ++++ src/main/resources/META-INF/spring.factories | 2 +- ...FlattenedEnvironmentPostProcessorTest.java | 94 +++++++++++++++++++ ...SpecificEnvironmentPostProcessorTest.java} | 53 ++--------- .../boot/PropertySourceContributorTest.java | 59 ++++++++++++ 22 files changed, 381 insertions(+), 148 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/bindings/boot/BindingFlattenedEnvironmentPostProcessor.java rename src/main/java/org/springframework/cloud/bindings/boot/{BindingsEnvironmentPostProcessor.java => BindingSpecificEnvironmentPostProcessor.java} (65%) create mode 100644 src/main/java/org/springframework/cloud/bindings/boot/PropertySourceContributor.java create mode 100644 src/main/java/org/springframework/cloud/bindings/boot/package-info.java create mode 100644 src/main/java/org/springframework/cloud/bindings/package-info.java create mode 100644 src/test/java/org/springframework/cloud/bindings/boot/BindingFlattenedEnvironmentPostProcessorTest.java rename src/test/java/org/springframework/cloud/bindings/boot/{BindingsEnvironmentPostProcessorTest.java => BindingSpecificEnvironmentPostProcessorTest.java} (60%) create mode 100644 src/test/java/org/springframework/cloud/bindings/boot/PropertySourceContributorTest.java diff --git a/pom.xml b/pom.xml index 10fe53a..1a94f9c 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 1.8 - 19.0.0 + 3.0.2 0.6.0 2.2.7.RELEASE @@ -54,9 +54,9 @@ - org.jetbrains - annotations - ${jetbrains-annotations.version} + com.google.code.findbugs + jsr305 + ${jsr305.version} provided diff --git a/src/main/java/org/springframework/cloud/bindings/Binding.java b/src/main/java/org/springframework/cloud/bindings/Binding.java index a2156df..9f3f3db 100644 --- a/src/main/java/org/springframework/cloud/bindings/Binding.java +++ b/src/main/java/org/springframework/cloud/bindings/Binding.java @@ -15,8 +15,6 @@ */ package org.springframework.cloud.bindings; -import org.jetbrains.annotations.NotNull; - import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -42,7 +40,7 @@ public final class Binding { /** * Creates a new {@code Binding} instance using the specified file system root. */ - public Binding(@NotNull Path path) { + public Binding(Path path) { this.name = path.getFileName().toString(); this.path = path; this.metadata = createFilePerEntryMap(path.resolve("metadata")); @@ -57,9 +55,7 @@ public final class Binding { * @param metadata the metadata of the {@code Binding}. * @param secret the secret of the {@code Binding}. */ - public Binding(@NotNull String name, @NotNull Path path, @NotNull Map metadata, - @NotNull Map secret) { - + public Binding(String name, Path path, Map metadata, Map secret) { this.name = name; this.path = path; this.metadata = metadata; @@ -69,42 +65,42 @@ public final class Binding { /** * Returns the name of the binding. */ - public @NotNull String getName() { + public String getName() { return name; } /** * Returns the path of the binding. */ - public @NotNull Path getPath() { + public Path getPath() { return path; } /** * Returns the metadata of the binding. */ - public @NotNull Map getMetadata() { + public Map getMetadata() { return metadata; } /** * Returns the secret of the binding. */ - public @NotNull Map getSecret() { + public Map getSecret() { return secret; } /** * Returns the kind of the binding. Equivalent to {@code getMetadata().get("kind")}. */ - public @NotNull String getKind() { + public String getKind() { return metadata.get("kind"); } /** * Returns the provider of the binding. Equivalent to {@code getMetadata().get("provider")}. */ - public @NotNull String getProvider() { + public String getProvider() { return metadata.get("provider"); } @@ -113,7 +109,7 @@ public final class Binding { * * @param name the name of the metadata key. */ - public @NotNull Path getMetadataFilePath(@NotNull String name) { + public Path getMetadataFilePath(String name) { return this.path.resolve("metadata").resolve(name); } @@ -122,7 +118,7 @@ public final class Binding { * * @param name the name of the secret key. */ - public @NotNull Path getSecretFilePath(@NotNull String name) { + public Path getSecretFilePath(String name) { return this.path.resolve("secret").resolve(name); } @@ -142,7 +138,7 @@ public final class Binding { return Objects.hash(name, path, metadata, secret); } - private @NotNull Map createFilePerEntryMap(@NotNull Path path) { + private Map createFilePerEntryMap(Path path) { try { return Files.list(path) .collect(Collectors.toMap( diff --git a/src/main/java/org/springframework/cloud/bindings/Bindings.java b/src/main/java/org/springframework/cloud/bindings/Bindings.java index da79c43..b597bb8 100644 --- a/src/main/java/org/springframework/cloud/bindings/Bindings.java +++ b/src/main/java/org/springframework/cloud/bindings/Bindings.java @@ -15,14 +15,12 @@ */ package org.springframework.cloud.bindings; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.springframework.lang.Nullable; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -58,7 +56,7 @@ public final class Bindings { * * @param path the path to populate the {@code Bindings} from. */ - public Bindings(String path) { + public Bindings(@Nullable String path) { if (path == null) { this.bindings = Collections.emptyList(); return; @@ -89,14 +87,14 @@ public final class Bindings { * * @param bindings the {@code Binding}s. */ - public Bindings(@NotNull Binding... bindings) { + public Bindings(Binding... bindings) { this.bindings = Arrays.asList(bindings); } /** * Returns all the {@link Binding}s that were found during construction. */ - public @NotNull List getBindings() { + public List getBindings() { return bindings; } @@ -106,14 +104,12 @@ public final class Bindings { * @param name the name of the {@code Binding} to find. * @return the {@code Binding} with a given name if it exists, {@code null} otherwise. */ - public @Nullable Binding findBinding(@NotNull String name) { - for (Binding binding : bindings) { - if (binding.getName().equalsIgnoreCase(name)) { - return binding; - } - } - - return null; + @Nullable + public Binding findBinding(String name) { + return bindings.stream() + .filter(binding -> binding.getName().equalsIgnoreCase(name)) + .findFirst() + .orElse(null); } /** @@ -122,7 +118,7 @@ public final class Bindings { * @param kind the kind of the {@code Binding} to find. * @return the collection of {@code Binding}s with a given kind. */ - public @NotNull List filterBindings(@Nullable String kind) { + public List filterBindings(@Nullable String kind) { return filterBindings(kind, null); } @@ -134,18 +130,12 @@ public final class Bindings { * @param provider the provider of {@code Binding} to find * @return the collection of {@code Binding}s with a given kind and provider. */ - public @NotNull List filterBindings(@Nullable String kind, @Nullable String provider) { - List filtered = new ArrayList<>(); - - for (Binding binding : bindings) { - if ((kind == null || binding.getKind().equalsIgnoreCase(kind)) && - (provider == null) || binding.getProvider().equalsIgnoreCase(provider)) { - - filtered.add(binding); - } - } - - return filtered; + public List filterBindings(@Nullable String kind, @Nullable String provider) { + return bindings.stream() + .filter(binding -> + (kind == null || binding.getKind().equalsIgnoreCase(kind)) && + (provider == null) || binding.getProvider().equalsIgnoreCase(provider)) + .collect(Collectors.toList()); } } diff --git a/src/main/java/org/springframework/cloud/bindings/boot/BindingFlattenedEnvironmentPostProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/BindingFlattenedEnvironmentPostProcessor.java new file mode 100644 index 0000000..69ff7e8 --- /dev/null +++ b/src/main/java/org/springframework/cloud/bindings/boot/BindingFlattenedEnvironmentPostProcessor.java @@ -0,0 +1,84 @@ +/* + * Copyright 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 + * + * http://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.bindings.boot; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.config.ConfigFileApplicationListener; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.cloud.bindings.Bindings; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; + +import java.util.HashMap; +import java.util.Map; + +import static org.springframework.cloud.bindings.boot.PropertySourceContributor.contributePropertySource; + +/** + * An implementation of {@link EnvironmentPostProcessor} that generates properties from {@link Bindings} with a + * flattened format: {@code cnb.bindings.{name}.{metadata,secret}.*}. + */ +public final class BindingFlattenedEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { + + public static final String BINDING_FLATTENED_PROPERTY_SOURCE_NAME = "cnbBindingFlattened"; + + private final Log log = LogFactory.getLog(getClass()); + + private final Bindings bindings; + + /** + * Creates a new instance of {@code BindingFlattenedEnvironmentPostProcessor} using the {@link Bindings} available + * in the environment. + */ + public BindingFlattenedEnvironmentPostProcessor() { + this(new Bindings()); + } + + BindingFlattenedEnvironmentPostProcessor(Bindings bindings) { + this.bindings = bindings; + } + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + Map properties = new HashMap<>(); + bindings.getBindings().forEach(binding -> { + binding.getMetadata().forEach((key, value) -> { + properties.put(String.format("cnb.bindings.%s.metadata.%s", binding.getName(), key), value); + }); + binding.getSecret().forEach((key, value) -> { + properties.put(String.format("cnb.bindings.%s.secret.%s", binding.getName(), key), value); + }); + }); + + if (properties.isEmpty()) { + log.debug("No properties set from CNB Bindings. Skipping PropertySource creation."); + return; + } + + log.info("Creating flattened PropertySource from CNB Bindings"); + contributePropertySource(BINDING_FLATTENED_PROPERTY_SOURCE_NAME, properties, environment); + } + + @Override + public int getOrder() { + // Before ConfigFileApplicationListener so values there can use values from {@link Bindings}. + return ConfigFileApplicationListener.DEFAULT_ORDER - 1; + } + +} diff --git a/src/main/java/org/springframework/cloud/bindings/boot/BindingsEnvironmentPostProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessor.java similarity index 65% rename from src/main/java/org/springframework/cloud/bindings/boot/BindingsEnvironmentPostProcessor.java rename to src/main/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessor.java index c4c6515..6e57def 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/BindingsEnvironmentPostProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessor.java @@ -18,7 +18,6 @@ package org.springframework.cloud.bindings.boot; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.jetbrains.annotations.NotNull; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.boot.env.EnvironmentPostProcessor; @@ -26,8 +25,6 @@ import org.springframework.cloud.bindings.Bindings; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.io.support.SpringFactoriesLoader; @@ -37,7 +34,7 @@ import java.util.List; import java.util.Map; import static org.springframework.cloud.bindings.boot.Guards.isGlobalEnabled; -import static org.springframework.core.env.CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; +import static org.springframework.cloud.bindings.boot.PropertySourceContributor.contributePropertySource; /** * An implementation of {@link EnvironmentPostProcessor} that delegates properties generation from {@link Bindings}. @@ -45,14 +42,15 @@ import static org.springframework.core.env.CommandLinePropertySource.COMMAND_LIN * {@link BindingsPropertiesProcessor} registered with {@link SpringFactoriesLoader} allowing them to generate any * properties from the contents of the {@code Bindings}. *

- * Must be enabled by setting the {@code org.springframework.cloud.bindings.boot.enable} System Property to {@code true}. + * Must be enabled by setting the {@code org.springframework.cloud.bindings.boot.enable} System Property to + * {@code true}. */ -public final class BindingsEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { +public final class BindingSpecificEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { /** * The name of the {@link PropertySource} created by the {@code BindingsEnvironmentPostProcessor}: {@value}. */ - public static final String BINDINGS_PROPERTY_SOURCE_NAME = "cnbBindings"; + public static final String BINDING_SPECIFIC_PROPERTY_SOURCE_NAME = "cnbBindingSpecific"; final List processors; @@ -61,27 +59,23 @@ public final class BindingsEnvironmentPostProcessor implements EnvironmentPostPr private final Bindings bindings; /** - * Creates a new instance of {@code BindingsEnvironmentPostProcessor} using the {@link Bindings} available in the - * environment and the {@link BindingsPropertiesProcessor}s registered with {@link SpringFactoriesLoader}. + * Creates a new instance of {@code BindingSpecificEnvironmentPostProcessor} using the {@link Bindings} available in + * the environment and the {@link BindingsPropertiesProcessor}s registered with {@link SpringFactoriesLoader}. */ - public BindingsEnvironmentPostProcessor() { + public BindingSpecificEnvironmentPostProcessor() { this.bindings = new Bindings(); this.processors = SpringFactoriesLoader. loadFactories(BindingsPropertiesProcessor.class, getClass().getClassLoader()); AnnotationAwareOrderComparator.sort(this.processors); } - BindingsEnvironmentPostProcessor(@NotNull Bindings bindings, - @NotNull BindingsPropertiesProcessor... processors) { - + BindingSpecificEnvironmentPostProcessor(Bindings bindings, BindingsPropertiesProcessor... processors) { this.bindings = bindings; this.processors = Arrays.asList(processors); } @Override - public void postProcessEnvironment(@NotNull ConfigurableEnvironment environment, - @NotNull SpringApplication application) { - + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { if (!isGlobalEnabled()) { return; } @@ -92,23 +86,14 @@ public final class BindingsEnvironmentPostProcessor implements EnvironmentPostPr } Map properties = new HashMap<>(); - for (BindingsPropertiesProcessor processor : processors) { - processor.process(bindings, properties); - } + processors.forEach(processor -> processor.process(bindings, properties)); if (properties.isEmpty()) { log.debug("No properties set from CNB Bindings. Skipping PropertySource creation."); return; } - log.info("Creating PropertySource from CNB Bindings"); - MutablePropertySources propertySources = environment.getPropertySources(); - MapPropertySource propertySource = new MapPropertySource(BINDINGS_PROPERTY_SOURCE_NAME, properties); - - if (propertySources.contains(COMMAND_LINE_PROPERTY_SOURCE_NAME)) { - propertySources.addAfter(COMMAND_LINE_PROPERTY_SOURCE_NAME, propertySource); - } else { - propertySources.addFirst(propertySource); - } + log.info("Creating binding-specific PropertySource from CNB Bindings"); + contributePropertySource(BINDING_SPECIFIC_PROPERTY_SOURCE_NAME, properties, environment); } @Override diff --git a/src/main/java/org/springframework/cloud/bindings/boot/BindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/BindingsPropertiesProcessor.java index c4a6e6b..e897e2e 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/BindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/BindingsPropertiesProcessor.java @@ -17,7 +17,6 @@ package org.springframework.cloud.bindings.boot; import org.springframework.cloud.bindings.Bindings; -import org.springframework.lang.NonNull; import java.util.Map; @@ -34,6 +33,6 @@ public interface BindingsPropertiesProcessor { * @param bindings the {@code Bindings} exposed to the application. * @param properties the currently accumulated properties. */ - void process(@NonNull Bindings bindings, @NonNull Map properties); + void process(Bindings bindings, Map properties); } diff --git a/src/main/java/org/springframework/cloud/bindings/boot/CassandraBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/CassandraBindingsPropertiesProcessor.java index 34ed5ec..0c3cf45 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/CassandraBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/CassandraBindingsPropertiesProcessor.java @@ -16,10 +16,8 @@ package org.springframework.cloud.bindings.boot; -import org.jetbrains.annotations.NotNull; import org.springframework.cloud.bindings.Binding; import org.springframework.cloud.bindings.Bindings; -import org.springframework.lang.NonNull; import java.util.Map; @@ -36,7 +34,7 @@ public final class CassandraBindingsPropertiesProcessor implements BindingsPrope public static final String KIND = "Cassandra"; @Override - public void process(@NonNull Bindings bindings, @NotNull Map properties) { + public void process(Bindings bindings, Map properties) { if (!isKindEnabled(KIND)) { return; } diff --git a/src/main/java/org/springframework/cloud/bindings/boot/Db2BindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/Db2BindingsPropertiesProcessor.java index 0fe5fd5..b29d5e7 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/Db2BindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/Db2BindingsPropertiesProcessor.java @@ -16,10 +16,8 @@ package org.springframework.cloud.bindings.boot; -import org.jetbrains.annotations.NotNull; import org.springframework.cloud.bindings.Binding; import org.springframework.cloud.bindings.Bindings; -import org.springframework.lang.NonNull; import java.util.Map; @@ -38,7 +36,7 @@ public final class Db2BindingsPropertiesProcessor implements BindingsPropertiesP public static final String KIND = "DB2"; @Override - public void process(@NonNull Bindings bindings, @NotNull Map properties) { + public void process(Bindings bindings, Map properties) { if (!isKindEnabled(KIND)) { return; } diff --git a/src/main/java/org/springframework/cloud/bindings/boot/Guards.java b/src/main/java/org/springframework/cloud/bindings/boot/Guards.java index d89524d..5718c78 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/Guards.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/Guards.java @@ -16,8 +16,6 @@ package org.springframework.cloud.bindings.boot; -import org.jetbrains.annotations.NotNull; - final class Guards { static boolean isGlobalEnabled() { @@ -25,7 +23,7 @@ final class Guards { return Boolean.parseBoolean(value); } - static boolean isKindEnabled(@NotNull String kind) { + static boolean isKindEnabled(String kind) { String property = String.format("org.springframework.cloud.bindings.boot.%s.enable", kind.toLowerCase()); String value = System.getProperty(property, "true"); return Boolean.parseBoolean(value); diff --git a/src/main/java/org/springframework/cloud/bindings/boot/MongoDbBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/MongoDbBindingsPropertiesProcessor.java index 10e49d7..5105082 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/MongoDbBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/MongoDbBindingsPropertiesProcessor.java @@ -18,7 +18,6 @@ package org.springframework.cloud.bindings.boot; import org.springframework.cloud.bindings.Binding; import org.springframework.cloud.bindings.Bindings; -import org.springframework.lang.NonNull; import java.util.Map; @@ -35,7 +34,7 @@ public final class MongoDbBindingsPropertiesProcessor implements BindingsPropert public static final String KIND = "MongoDB"; @Override - public void process(@NonNull Bindings bindings, @NonNull Map properties) { + public void process(Bindings bindings, Map properties) { if (!isKindEnabled(KIND)) { return; } diff --git a/src/main/java/org/springframework/cloud/bindings/boot/MySqlBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/MySqlBindingsPropertiesProcessor.java index b4f6596..6440d37 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/MySqlBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/MySqlBindingsPropertiesProcessor.java @@ -16,10 +16,8 @@ package org.springframework.cloud.bindings.boot; -import org.jetbrains.annotations.NotNull; import org.springframework.cloud.bindings.Binding; import org.springframework.cloud.bindings.Bindings; -import org.springframework.lang.NonNull; import java.util.Map; @@ -38,7 +36,7 @@ public final class MySqlBindingsPropertiesProcessor implements BindingsPropertie public static final String KIND = "MySQL"; @Override - public void process(@NonNull Bindings bindings, @NotNull Map properties) { + public void process(Bindings bindings, Map properties) { if (!isKindEnabled(KIND)) { return; } diff --git a/src/main/java/org/springframework/cloud/bindings/boot/OracleBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/OracleBindingsPropertiesProcessor.java index e851cb0..813f019 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/OracleBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/OracleBindingsPropertiesProcessor.java @@ -16,10 +16,8 @@ package org.springframework.cloud.bindings.boot; -import org.jetbrains.annotations.NotNull; import org.springframework.cloud.bindings.Binding; import org.springframework.cloud.bindings.Bindings; -import org.springframework.lang.NonNull; import java.util.Map; @@ -38,7 +36,7 @@ public final class OracleBindingsPropertiesProcessor implements BindingsProperti public static final String KIND = "Oracle"; @Override - public void process(@NonNull Bindings bindings, @NotNull Map properties) { + public void process(Bindings bindings, Map properties) { if (!isKindEnabled(KIND)) { return; } diff --git a/src/main/java/org/springframework/cloud/bindings/boot/PostgreSqlBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/PostgreSqlBindingsPropertiesProcessor.java index b29c93a..b4a8ad6 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/PostgreSqlBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/PostgreSqlBindingsPropertiesProcessor.java @@ -16,10 +16,8 @@ package org.springframework.cloud.bindings.boot; -import org.jetbrains.annotations.NotNull; import org.springframework.cloud.bindings.Binding; import org.springframework.cloud.bindings.Bindings; -import org.springframework.lang.NonNull; import java.util.Map; @@ -38,7 +36,7 @@ public final class PostgreSqlBindingsPropertiesProcessor implements BindingsProp public static final String KIND = "PostgreSQL"; @Override - public void process(@NonNull Bindings bindings, @NotNull Map properties) { + public void process(Bindings bindings, Map properties) { if (!isKindEnabled(KIND)) { return; } diff --git a/src/main/java/org/springframework/cloud/bindings/boot/PropertySourceContributor.java b/src/main/java/org/springframework/cloud/bindings/boot/PropertySourceContributor.java new file mode 100644 index 0000000..ae6a395 --- /dev/null +++ b/src/main/java/org/springframework/cloud/bindings/boot/PropertySourceContributor.java @@ -0,0 +1,40 @@ +/* + * Copyright 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 + * + * http://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.bindings.boot; + +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; + +import java.util.Map; + +import static org.springframework.core.env.CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; + +final class PropertySourceContributor { + + static void contributePropertySource(String name, Map properties, ConfigurableEnvironment environment) { + MapPropertySource propertySource = new MapPropertySource(name, properties); + + MutablePropertySources propertySources = environment.getPropertySources(); + if (propertySources.contains(COMMAND_LINE_PROPERTY_SOURCE_NAME)) { + propertySources.addAfter(COMMAND_LINE_PROPERTY_SOURCE_NAME, propertySource); + } else { + propertySources.addFirst(propertySource); + } + } + +} diff --git a/src/main/java/org/springframework/cloud/bindings/boot/RedisBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/RedisBindingsPropertiesProcessor.java index e595fe7..7ce95c0 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/RedisBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/RedisBindingsPropertiesProcessor.java @@ -16,10 +16,8 @@ package org.springframework.cloud.bindings.boot; -import org.jetbrains.annotations.NotNull; import org.springframework.cloud.bindings.Binding; import org.springframework.cloud.bindings.Bindings; -import org.springframework.lang.NonNull; import java.util.Map; @@ -36,7 +34,7 @@ public final class RedisBindingsPropertiesProcessor implements BindingsPropertie public static final String KIND = "Redis"; @Override - public void process(@NonNull Bindings bindings, @NotNull Map properties) { + public void process(Bindings bindings, Map properties) { if (!isKindEnabled(KIND)) { return; } diff --git a/src/main/java/org/springframework/cloud/bindings/boot/SqlServerBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/SqlServerBindingsPropertiesProcessor.java index f817dd0..aca4a51 100644 --- a/src/main/java/org/springframework/cloud/bindings/boot/SqlServerBindingsPropertiesProcessor.java +++ b/src/main/java/org/springframework/cloud/bindings/boot/SqlServerBindingsPropertiesProcessor.java @@ -16,10 +16,8 @@ package org.springframework.cloud.bindings.boot; -import org.jetbrains.annotations.NotNull; import org.springframework.cloud.bindings.Binding; import org.springframework.cloud.bindings.Bindings; -import org.springframework.lang.NonNull; import java.util.Map; @@ -38,7 +36,7 @@ public final class SqlServerBindingsPropertiesProcessor implements BindingsPrope public static final String KIND = "SQLServer"; @Override - public void process(@NonNull Bindings bindings, @NotNull Map properties) { + public void process(Bindings bindings, Map properties) { if (!isKindEnabled(KIND)) { return; } diff --git a/src/main/java/org/springframework/cloud/bindings/boot/package-info.java b/src/main/java/org/springframework/cloud/bindings/boot/package-info.java new file mode 100644 index 0000000..ed8f3cf --- /dev/null +++ b/src/main/java/org/springframework/cloud/bindings/boot/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 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 + * + * http://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. + */ + +@NonNullApi +package org.springframework.cloud.bindings.boot; + +import org.springframework.lang.NonNullApi; diff --git a/src/main/java/org/springframework/cloud/bindings/package-info.java b/src/main/java/org/springframework/cloud/bindings/package-info.java new file mode 100644 index 0000000..fe04274 --- /dev/null +++ b/src/main/java/org/springframework/cloud/bindings/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 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 + * + * http://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. + */ + +@NonNullApi +package org.springframework.cloud.bindings; + +import org.springframework.lang.NonNullApi; diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories index 7d0a8b6..bf99c8f 100644 --- a/src/main/resources/META-INF/spring.factories +++ b/src/main/resources/META-INF/spring.factories @@ -1,5 +1,5 @@ org.springframework.boot.env.EnvironmentPostProcessor=\ - org.springframework.cloud.bindings.boot.BindingsEnvironmentPostProcessor + org.springframework.cloud.bindings.boot.BindingSpecificEnvironmentPostProcessor # Included implementations org.springframework.cloud.bindings.boot.BindingsPropertiesProcessor=\ org.springframework.cloud.bindings.boot.CassandraBindingsPropertiesProcessor, \ diff --git a/src/test/java/org/springframework/cloud/bindings/boot/BindingFlattenedEnvironmentPostProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/BindingFlattenedEnvironmentPostProcessorTest.java new file mode 100644 index 0000000..699bba5 --- /dev/null +++ b/src/test/java/org/springframework/cloud/bindings/boot/BindingFlattenedEnvironmentPostProcessorTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 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 + * + * http://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.bindings.boot; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ClearSystemProperty; +import org.junitpioneer.jupiter.SetSystemProperty; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.config.ConfigFileApplicationListener; +import org.springframework.cloud.bindings.Binding; +import org.springframework.cloud.bindings.Bindings; +import org.springframework.mock.env.MockEnvironment; + +import java.nio.file.Paths; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("Binding-flattened EnvironmentPostProcessor") +final class BindingFlattenedEnvironmentPostProcessorTest { + + private final SpringApplication application = new SpringApplication(); + + private final MockEnvironment environment = new MockEnvironment(); + + @Test + @DisplayName("is disabled by default") + @ClearSystemProperty(key = "org.springframework.cloud.bindings.boot.enable") + void disabledByDefault() { + new BindingFlattenedEnvironmentPostProcessor( + new Bindings( + new Binding("test-name", Paths.get("test-path"), + Collections.emptyMap(), Collections.emptyMap()) + ) + ).postProcessEnvironment(environment, application); + + assertThat(environment.getPropertySources()).hasSize(1); + } + + @Nested + @DisplayName("when enabled") + @SetSystemProperty(key = "org.springframework.cloud.bindings.boot.enable", value = "true") + final class Enabled { + + @Test + @DisplayName("does not create PropertySource if no bindings") + void noBindings() { + new BindingFlattenedEnvironmentPostProcessor(new Bindings()).postProcessEnvironment(environment, application); + + assertThat(environment.getPropertySources()).hasSize(1); + } + + @Test + @DisplayName("creates PropertySource with properties") + void containsProperties() { + new BindingFlattenedEnvironmentPostProcessor( + new Bindings( + new Binding("test-name", Paths.get("test-path"), + Collections.singletonMap("test-metadata-key", "test-metadata-value"), + Collections.singletonMap("test-secret-key", "test-secret-value")) + ) + ).postProcessEnvironment(environment, application); + + assertThat(environment.getPropertySources()).hasSize(2); + assertThat(environment.getProperty("cnb.bindings.test-name.metadata.test-metadata-key")).isEqualTo("test-metadata-value"); + assertThat(environment.getProperty("cnb.bindings.test-name.secret.test-secret-key")).isEqualTo("test-secret-value"); + } + + @Test + @DisplayName("has order before ConfigFileApplicationListener") + void order() { + assertThat(new BindingFlattenedEnvironmentPostProcessor(new Bindings()).getOrder()) + .isLessThan(ConfigFileApplicationListener.DEFAULT_ORDER); + } + + } + +} diff --git a/src/test/java/org/springframework/cloud/bindings/boot/BindingsEnvironmentPostProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java similarity index 60% rename from src/test/java/org/springframework/cloud/bindings/boot/BindingsEnvironmentPostProcessorTest.java rename to src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java index 8ca5ff0..5ba4d98 100644 --- a/src/test/java/org/springframework/cloud/bindings/boot/BindingsEnvironmentPostProcessorTest.java +++ b/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java @@ -25,18 +25,15 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.cloud.bindings.Binding; import org.springframework.cloud.bindings.Bindings; -import org.springframework.core.env.PropertySource; -import org.springframework.core.env.SimpleCommandLinePropertySource; import org.springframework.mock.env.MockEnvironment; import java.nio.file.Paths; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.bindings.boot.BindingsEnvironmentPostProcessor.BINDINGS_PROPERTY_SOURCE_NAME; -@DisplayName("Bindings EnvironmentPostProcessor") -final class BindingsEnvironmentPostProcessorTest { +@DisplayName("Binding-specific EnvironmentPostProcessor") +final class BindingSpecificEnvironmentPostProcessorTest { private final SpringApplication application = new SpringApplication(); @@ -46,7 +43,7 @@ final class BindingsEnvironmentPostProcessorTest { @DisplayName("is disabled by default") @ClearSystemProperty(key = "org.springframework.cloud.bindings.boot.enable") void disabledByDefault() { - new BindingsEnvironmentPostProcessor( + new BindingSpecificEnvironmentPostProcessor( new Bindings( new Binding("test-name", Paths.get("test-path"), Collections.emptyMap(), Collections.emptyMap()) @@ -65,7 +62,7 @@ final class BindingsEnvironmentPostProcessorTest { @Test @DisplayName("does not create PropertySource if no bindings") void noBindings() { - new BindingsEnvironmentPostProcessor(new Bindings()).postProcessEnvironment(environment, application); + new BindingSpecificEnvironmentPostProcessor(new Bindings()).postProcessEnvironment(environment, application); assertThat(environment.getPropertySources()).hasSize(1); } @@ -73,7 +70,7 @@ final class BindingsEnvironmentPostProcessorTest { @Test @DisplayName("does not create PropertySource if no properties") void noProperties() { - new BindingsEnvironmentPostProcessor( + new BindingSpecificEnvironmentPostProcessor( new Bindings( new Binding("test-name", Paths.get("test-path"), Collections.emptyMap(), Collections.emptyMap()) @@ -86,7 +83,7 @@ final class BindingsEnvironmentPostProcessorTest { @Test @DisplayName("creates PropertySource with properties") void containsProperties() { - new BindingsEnvironmentPostProcessor( + new BindingSpecificEnvironmentPostProcessor( new Bindings( new Binding("test-name", Paths.get("test-path"), Collections.emptyMap(), Collections.emptyMap()) @@ -98,51 +95,17 @@ final class BindingsEnvironmentPostProcessorTest { assertThat(environment.getProperty("test-key")).isEqualTo("test-value"); } - @Test - @DisplayName("adds PropertySource after CommandLinePropertySource") - void withCommandLinePropertySource() { - environment.getPropertySources().addFirst(new SimpleCommandLinePropertySource()); - - new BindingsEnvironmentPostProcessor( - new Bindings( - new Binding("test-name", Paths.get("test-path"), - Collections.emptyMap(), Collections.emptyMap()) - ), - (environment, properties) -> properties.put("test-key", "test-value") - ).postProcessEnvironment(environment, application); - - PropertySource propertySource = environment.getPropertySources().get(BINDINGS_PROPERTY_SOURCE_NAME); - assertThat(propertySource).isNotNull(); - assertThat(environment.getPropertySources().precedenceOf(propertySource)).isEqualTo(1); - } - - @Test - @DisplayName("adds PropertySource first") - void withoutCommandLinePropertySource() { - new BindingsEnvironmentPostProcessor( - new Bindings( - new Binding("test-name", Paths.get("test-path"), - Collections.emptyMap(), Collections.emptyMap()) - ), - (environment, properties) -> properties.put("test-key", "test-value") - ).postProcessEnvironment(environment, application); - - PropertySource propertySource = environment.getPropertySources().get(BINDINGS_PROPERTY_SOURCE_NAME); - assertThat(propertySource).isNotNull(); - assertThat(environment.getPropertySources().precedenceOf(propertySource)).isEqualTo(0); - } - @Test @DisplayName("has order before ConfigFileApplicationListener") void order() { - assertThat(new BindingsEnvironmentPostProcessor(new Bindings()).getOrder()) + assertThat(new BindingSpecificEnvironmentPostProcessor(new Bindings()).getOrder()) .isLessThan(ConfigFileApplicationListener.DEFAULT_ORDER); } @Test @DisplayName("included implementations are registered") void includedImplementations() { - assertThat(new BindingsEnvironmentPostProcessor().processors).hasSize(8); + assertThat(new BindingSpecificEnvironmentPostProcessor().processors).hasSize(8); } } diff --git a/src/test/java/org/springframework/cloud/bindings/boot/PropertySourceContributorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/PropertySourceContributorTest.java new file mode 100644 index 0000000..87d070e --- /dev/null +++ b/src/test/java/org/springframework/cloud/bindings/boot/PropertySourceContributorTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 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 + * + * http://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.bindings.boot; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.SimpleCommandLinePropertySource; +import org.springframework.mock.env.MockEnvironment; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.bindings.boot.PropertySourceContributor.contributePropertySource; + +@DisplayName("PropertySource Contributor") +final class PropertySourceContributorTest { + + private final MockEnvironment environment = new MockEnvironment(); + + @Test + @DisplayName("adds PropertySource after CommandLinePropertySource") + void withCommandLinePropertySource() { + environment.getPropertySources().addFirst(new SimpleCommandLinePropertySource()); + + contributePropertySource("test-name", Collections.singletonMap("test-key", "test-value"), environment); + + PropertySource propertySource = environment.getPropertySources().get("test-name"); + assertThat(propertySource).isNotNull(); + assertThat(propertySource.getProperty("test-key")).isEqualTo("test-value"); + assertThat(environment.getPropertySources().precedenceOf(propertySource)).isEqualTo(1); + } + + @Test + @DisplayName("adds PropertySource first") + void withoutCommandLinePropertySource() { + contributePropertySource("test-name", Collections.singletonMap("test-key", "test-value"), environment); + + PropertySource propertySource = environment.getPropertySources().get("test-name"); + assertThat(propertySource).isNotNull(); + assertThat(propertySource.getProperty("test-key")).isEqualTo("test-value"); + assertThat(environment.getPropertySources().precedenceOf(propertySource)).isEqualTo(0); + } + +}