From cd3ddc2799c3af2be186b78eafea9fb17b179e58 Mon Sep 17 00:00:00 2001 From: Ben Hale Date: Sat, 9 May 2020 07:24:22 -0700 Subject: [PATCH] BindingsEnvironmentPostProcessor The previous implementation was *so* complex! I realize that you grabbed it from the previous implementation, but you ran into one of our biggest failings, too much complexity, too early. Among other things the looping priority was inverted (looping on the wrong thing). This simplifies the whole into a handful of lines, and once again notice the judicious use of constructors to make it testable. Signed-off-by: Ben Hale --- .../boot/CassandraCnbBindingProcessor.java | 2 +- .../cloud/cnb/boot/CnbBindingProcessor.java | 2 +- .../cnb/boot/CnbBindingsPostProcessor.java | 4 +- .../boot/DataSourceCnbBindingProcessor.java | 2 +- .../cnb/boot/MongoCnbBindingProcessor.java | 2 +- .../cnb/boot/RedisCnbBindingProcessor.java | 2 +- .../CassandraCnbBindingProcessorTests.java | 2 +- .../DataSourceCnbBindingProcessorTests.java | 2 +- .../boot/MongoCnbBindingProcessorTests.java | 2 +- .../boot/RedisCnbBindingProcessorTests.java | 2 +- .../cloud/cnb/boot/test/EnvMock.java | 2 +- .../cloud/cnb/boot/test/TestJdbcKind.java | 2 +- .../cloud/cnb/jdbc/DB2JdbcKind.java | 2 +- .../cloud/cnb/jdbc/JdbcBinding.java | 2 +- .../cloud/cnb/jdbc/JdbcKind.java | 2 +- .../cloud/cnb/jdbc/MysqlJdbcKind.java | 2 +- .../cloud/cnb/jdbc/OracleJdbcKind.java | 2 +- .../cloud/cnb/jdbc/PostgresJdbcKind.java | 2 +- .../cloud/cnb/jdbc/SqlServerJdbcKind.java | 2 +- .../cloud/cnb/jdbc/JdbcBindingTest.java | 2 +- cnb-bindings/pom.xml | 21 ++++ .../cloud/{cnb/core => bindings}/Binding.java | 4 +- .../{cnb/core => bindings}/Bindings.java | 53 ++++---- .../BindingsEnvironmentPostProcessor.java | 112 +++++++++++++++++ .../bindings/BindingsPropertiesProcessor.java | 38 ++++++ .../main/resources/META-INF/spring.factories | 2 + .../{cnb/core => bindings}/BindingTest.java | 7 +- .../BindingsEnvironmentPostProcessorTest.java | 116 ++++++++++++++++++ .../cloud/bindings/BindingsTests.java | 113 +++++++++++++++++ .../cloud/cnb/core/BindingsTests.java | 82 ------------- 30 files changed, 456 insertions(+), 134 deletions(-) rename cnb-bindings/src/main/java/org/springframework/cloud/{cnb/core => bindings}/Binding.java (98%) rename cnb-bindings/src/main/java/org/springframework/cloud/{cnb/core => bindings}/Bindings.java (77%) create mode 100644 cnb-bindings/src/main/java/org/springframework/cloud/bindings/BindingsEnvironmentPostProcessor.java create mode 100644 cnb-bindings/src/main/java/org/springframework/cloud/bindings/BindingsPropertiesProcessor.java create mode 100644 cnb-bindings/src/main/resources/META-INF/spring.factories rename cnb-bindings/src/test/java/org/springframework/cloud/{cnb/core => bindings}/BindingTest.java (85%) create mode 100644 cnb-bindings/src/test/java/org/springframework/cloud/bindings/BindingsEnvironmentPostProcessorTest.java create mode 100644 cnb-bindings/src/test/java/org/springframework/cloud/bindings/BindingsTests.java delete mode 100644 cnb-bindings/src/test/java/org/springframework/cloud/cnb/core/BindingsTests.java diff --git a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CassandraCnbBindingProcessor.java b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CassandraCnbBindingProcessor.java index 2add981..24b20b1 100644 --- a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CassandraCnbBindingProcessor.java +++ b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CassandraCnbBindingProcessor.java @@ -15,7 +15,7 @@ */ package org.springframework.cloud.cnb.boot; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; import java.util.Map; diff --git a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CnbBindingProcessor.java b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CnbBindingProcessor.java index 3fffaee..1c4f23e 100644 --- a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CnbBindingProcessor.java +++ b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CnbBindingProcessor.java @@ -17,7 +17,7 @@ package org.springframework.cloud.cnb.boot; import java.util.Map; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; public interface CnbBindingProcessor { diff --git a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CnbBindingsPostProcessor.java b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CnbBindingsPostProcessor.java index 427e21c..585032c 100644 --- a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CnbBindingsPostProcessor.java +++ b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/CnbBindingsPostProcessor.java @@ -25,8 +25,8 @@ import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.cloud.cnb.core.CNBBindingsSingleton; -import org.springframework.cloud.cnb.core.Binding; -import org.springframework.cloud.cnb.core.Bindings; +import org.springframework.cloud.cnb.Binding; +import org.springframework.cloud.cnb.Bindings; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; diff --git a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/DataSourceCnbBindingProcessor.java b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/DataSourceCnbBindingProcessor.java index 4f1595a..cf0de3f 100644 --- a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/DataSourceCnbBindingProcessor.java +++ b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/DataSourceCnbBindingProcessor.java @@ -17,7 +17,7 @@ package org.springframework.cloud.cnb.boot; import java.util.Map; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; import org.springframework.cloud.cnb.jdbc.JdbcBinding; diff --git a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/MongoCnbBindingProcessor.java b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/MongoCnbBindingProcessor.java index 44b7531..1baae33 100644 --- a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/MongoCnbBindingProcessor.java +++ b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/MongoCnbBindingProcessor.java @@ -17,7 +17,7 @@ package org.springframework.cloud.cnb.boot; import java.util.Map; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; public class MongoCnbBindingProcessor implements CnbBindingProcessor { private static final String MONGO_KIND = "mongodb"; diff --git a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/RedisCnbBindingProcessor.java b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/RedisCnbBindingProcessor.java index 12445db..616a813 100644 --- a/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/RedisCnbBindingProcessor.java +++ b/cnb-bindings-boot/src/main/java/org/springframework/cloud/cnb/boot/RedisCnbBindingProcessor.java @@ -17,7 +17,7 @@ package org.springframework.cloud.cnb.boot; import java.util.Map; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; public class RedisCnbBindingProcessor implements CnbBindingProcessor { diff --git a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/CassandraCnbBindingProcessorTests.java b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/CassandraCnbBindingProcessorTests.java index 2122748..11cdc58 100644 --- a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/CassandraCnbBindingProcessorTests.java +++ b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/CassandraCnbBindingProcessorTests.java @@ -20,7 +20,7 @@ import java.util.Map; import org.junit.Test; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; import static org.assertj.core.api.Assertions.assertThat; diff --git a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/DataSourceCnbBindingProcessorTests.java b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/DataSourceCnbBindingProcessorTests.java index e4f338a..6dd57a8 100644 --- a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/DataSourceCnbBindingProcessorTests.java +++ b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/DataSourceCnbBindingProcessorTests.java @@ -21,7 +21,7 @@ import java.util.Map; import org.junit.Test; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; import static org.assertj.core.api.Assertions.assertThat; diff --git a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/MongoCnbBindingProcessorTests.java b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/MongoCnbBindingProcessorTests.java index 662e61f..9e158ba 100644 --- a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/MongoCnbBindingProcessorTests.java +++ b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/MongoCnbBindingProcessorTests.java @@ -21,7 +21,7 @@ import java.util.Map; import org.junit.Test; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; import static org.assertj.core.api.Assertions.assertThat; diff --git a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/RedisCnbBindingProcessorTests.java b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/RedisCnbBindingProcessorTests.java index e98027f..9f089ca 100644 --- a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/RedisCnbBindingProcessorTests.java +++ b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/RedisCnbBindingProcessorTests.java @@ -21,7 +21,7 @@ import java.util.Map; import org.junit.Test; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; import static org.assertj.core.api.Assertions.assertThat; diff --git a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/test/EnvMock.java b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/test/EnvMock.java index 5d15abe..69a2cd0 100644 --- a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/test/EnvMock.java +++ b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/test/EnvMock.java @@ -21,7 +21,7 @@ import java.util.Map; import mockit.MockUp; -import org.springframework.cloud.cnb.core.Bindings; +import org.springframework.cloud.cnb.Bindings; /** diff --git a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/test/TestJdbcKind.java b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/test/TestJdbcKind.java index 6710e3e..a9c61f6 100644 --- a/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/test/TestJdbcKind.java +++ b/cnb-bindings-boot/src/test/java/org/springframework/cloud/cnb/boot/test/TestJdbcKind.java @@ -15,7 +15,7 @@ */ package org.springframework.cloud.cnb.boot.test; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; import org.springframework.cloud.cnb.jdbc.JdbcKind; public class TestJdbcKind implements JdbcKind { diff --git a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/DB2JdbcKind.java b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/DB2JdbcKind.java index 999be7d..6c11445 100644 --- a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/DB2JdbcKind.java +++ b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/DB2JdbcKind.java @@ -15,7 +15,7 @@ */ package org.springframework.cloud.cnb.jdbc; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; public class DB2JdbcKind implements JdbcKind{ diff --git a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/JdbcBinding.java b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/JdbcBinding.java index bc1f6db..718b187 100644 --- a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/JdbcBinding.java +++ b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/JdbcBinding.java @@ -17,7 +17,7 @@ package org.springframework.cloud.cnb.jdbc; import java.util.ServiceLoader; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; import org.springframework.cloud.cnb.core.IllegalBindingException; diff --git a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/JdbcKind.java b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/JdbcKind.java index 0313141..4104342 100644 --- a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/JdbcKind.java +++ b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/JdbcKind.java @@ -15,7 +15,7 @@ */ package org.springframework.cloud.cnb.jdbc; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; public interface JdbcKind { boolean forBinding(Binding binding); diff --git a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/MysqlJdbcKind.java b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/MysqlJdbcKind.java index b958a6b..ff9fb96 100644 --- a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/MysqlJdbcKind.java +++ b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/MysqlJdbcKind.java @@ -15,7 +15,7 @@ */ package org.springframework.cloud.cnb.jdbc; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; public class MysqlJdbcKind implements JdbcKind{ diff --git a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/OracleJdbcKind.java b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/OracleJdbcKind.java index f7e76b5..6a50e89 100644 --- a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/OracleJdbcKind.java +++ b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/OracleJdbcKind.java @@ -15,7 +15,7 @@ */ package org.springframework.cloud.cnb.jdbc; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; public class OracleJdbcKind implements JdbcKind{ diff --git a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/PostgresJdbcKind.java b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/PostgresJdbcKind.java index cbe7acf..873f57e 100644 --- a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/PostgresJdbcKind.java +++ b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/PostgresJdbcKind.java @@ -18,7 +18,7 @@ package org.springframework.cloud.cnb.jdbc; import java.util.Arrays; import java.util.List; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; public class PostgresJdbcKind implements JdbcKind{ diff --git a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/SqlServerJdbcKind.java b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/SqlServerJdbcKind.java index 5b56171..70d95a3 100644 --- a/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/SqlServerJdbcKind.java +++ b/cnb-bindings-jdbc/src/main/java/org/springframework/cloud/cnb/jdbc/SqlServerJdbcKind.java @@ -15,7 +15,7 @@ */ package org.springframework.cloud.cnb.jdbc; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; public class SqlServerJdbcKind implements JdbcKind{ diff --git a/cnb-bindings-jdbc/src/test/java/org/springframework/cloud/cnb/jdbc/JdbcBindingTest.java b/cnb-bindings-jdbc/src/test/java/org/springframework/cloud/cnb/jdbc/JdbcBindingTest.java index b1aaa3e..2044002 100644 --- a/cnb-bindings-jdbc/src/test/java/org/springframework/cloud/cnb/jdbc/JdbcBindingTest.java +++ b/cnb-bindings-jdbc/src/test/java/org/springframework/cloud/cnb/jdbc/JdbcBindingTest.java @@ -20,7 +20,7 @@ import java.util.Map; import org.junit.Test; -import org.springframework.cloud.cnb.core.Binding; +import org.springframework.cloud.cnb.Binding; import static org.assertj.core.api.Assertions.assertThat; diff --git a/cnb-bindings/pom.xml b/cnb-bindings/pom.xml index 8c4ceed..e51fad8 100644 --- a/cnb-bindings/pom.xml +++ b/cnb-bindings/pom.xml @@ -1,4 +1,20 @@ + + @@ -20,6 +36,11 @@ annotations provided + + org.springframework.boot + spring-boot-starter + provided + org.springframework.boot diff --git a/cnb-bindings/src/main/java/org/springframework/cloud/cnb/core/Binding.java b/cnb-bindings/src/main/java/org/springframework/cloud/bindings/Binding.java similarity index 98% rename from cnb-bindings/src/main/java/org/springframework/cloud/cnb/core/Binding.java rename to cnb-bindings/src/main/java/org/springframework/cloud/bindings/Binding.java index 20c08b9..a2156df 100644 --- a/cnb-bindings/src/main/java/org/springframework/cloud/cnb/core/Binding.java +++ b/cnb-bindings/src/main/java/org/springframework/cloud/bindings/Binding.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 the original author or authors. + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.cloud.cnb.core; +package org.springframework.cloud.bindings; import org.jetbrains.annotations.NotNull; diff --git a/cnb-bindings/src/main/java/org/springframework/cloud/cnb/core/Bindings.java b/cnb-bindings/src/main/java/org/springframework/cloud/bindings/Bindings.java similarity index 77% rename from cnb-bindings/src/main/java/org/springframework/cloud/cnb/core/Bindings.java rename to cnb-bindings/src/main/java/org/springframework/cloud/bindings/Bindings.java index 4d2ba99..c0eb485 100644 --- a/cnb-bindings/src/main/java/org/springframework/cloud/cnb/core/Bindings.java +++ b/cnb-bindings/src/main/java/org/springframework/cloud/bindings/Bindings.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.cloud.cnb.core; +package org.springframework.cloud.bindings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,9 +23,9 @@ 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; -import java.util.Map; import java.util.stream.Collectors; @@ -58,35 +58,34 @@ public final class Bindings { * * @param path the path to populate the {@code Bindings} from. */ - public Bindings(@NotNull String path) { + public Bindings(String path) { + if (path == null) { + this.bindings = Collections.emptyList(); + return; + } + Path p = Paths.get(path); if (!Files.exists(p)) { this.bindings = Collections.emptyList(); - } else if (!Files.isDirectory(p)) { + return; + } + + if (!Files.isDirectory(p)) { throw new IllegalArgumentException(String.format("%s is not a directory", p)); - } else { - try { - this.bindings = Files.list(p) - .map(Binding::new) - .collect(Collectors.toList()); - } catch (IOException e) { - throw new IllegalStateException(String.format("unable to list children of '%s'", path), e); - } + } + + try { + this.bindings = Files.list(p) + .map(Binding::new) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new IllegalStateException(String.format("unable to list children of '%s'", path), e); } } - /** - * Indicates whether the {@code $CNB_BINDINGS} is and zero or more bindings will be available. - * - * @return {@code true} if {@code $CNB_BINDINGS} is set, {@code false} otherwise. - */ - public static boolean hasBindings() { - return hasBindings(System.getenv()); - } - - static boolean hasBindings(@NotNull Map environment) { - return environment.containsKey(CNB_BINDINGS); + Bindings(@NotNull Binding... bindings) { + this.bindings = Arrays.asList(bindings); } /** @@ -113,13 +112,13 @@ public final class Bindings { } /** - * Returns zero or more {@link Binding}s with a given kind. Equivalent to {@link #getBindings(String, String)}. + * Returns zero or more {@link Binding}s with a given kind. Equivalent to {@link #filterBindings(String, String)}. * * @param kind the kind of the {@code Binding} to find. * @return the collection of {@code Binding}s with a given kind. */ - public @NotNull List getBindings(@Nullable String kind) { - return getBindings(kind, null); + public @NotNull List filterBindings(@Nullable String kind) { + return filterBindings(kind, null); } /** @@ -130,7 +129,7 @@ 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 getBindings(@Nullable String kind, @Nullable String provider) { + public @NotNull List filterBindings(@Nullable String kind, @Nullable String provider) { List filtered = new ArrayList<>(); for (Binding binding : bindings) { diff --git a/cnb-bindings/src/main/java/org/springframework/cloud/bindings/BindingsEnvironmentPostProcessor.java b/cnb-bindings/src/main/java/org/springframework/cloud/bindings/BindingsEnvironmentPostProcessor.java new file mode 100644 index 0000000..c7e0189 --- /dev/null +++ b/cnb-bindings/src/main/java/org/springframework/cloud/bindings/BindingsEnvironmentPostProcessor.java @@ -0,0 +1,112 @@ +/* + * 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; + +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; +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; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.springframework.core.env.CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; + +/** + * An implementation of {@link EnvironmentPostProcessor} that delegates properties generation from {@link Bindings}. + * This implementation generates a single instance of {@code Bindings} and then calls all implementations of + * {@link BindingsPropertiesProcessor} registered with {@link SpringFactoriesLoader} allowing them to generate any + * properties from the contents of the {@code Bindings}. + */ +public final class BindingsEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { + + /** + * The name of the {@link PropertySource} created by the {@code BindingsEnvironmentPostProcessor}: {@value}. + */ + public static final String BINDINGS_PROPERTY_SOURCE_NAME = "cnbBindings"; + + private final Log log = LogFactory.getLog(getClass()); + + private final Bindings bindings; + + private final List processors; + + /** + * Creates a new instance of {@code BindingsEnvironmentPostProcessor} using the {@link Bindings} available in the + * environment and the {@link BindingsPropertiesProcessor}s registered with {@link SpringFactoriesLoader}. + */ + public BindingsEnvironmentPostProcessor() { + this.bindings = new Bindings(); + this.processors = SpringFactoriesLoader. + loadFactories(BindingsPropertiesProcessor.class, getClass().getClassLoader()); + AnnotationAwareOrderComparator.sort(this.processors); + } + + BindingsEnvironmentPostProcessor(@NotNull Bindings bindings, + @NotNull BindingsPropertiesProcessor... processors) { + + this.bindings = bindings; + this.processors = Arrays.asList(processors); + } + + @Override + public void postProcessEnvironment(@NotNull ConfigurableEnvironment environment, + @NotNull SpringApplication application) { + + if (bindings.getBindings().isEmpty()) { + log.debug("No CNB Bindings found. Skipping Environment post-processing."); + return; + } + + Map properties = new HashMap<>(); + for (BindingsPropertiesProcessor processor : processors) { + 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); + } + } + + @Override + public int getOrder() { + // Before ConfigFileApplicationListener so values there can use values from {@link Bindings}. + return ConfigFileApplicationListener.DEFAULT_ORDER - 1; + } + +} diff --git a/cnb-bindings/src/main/java/org/springframework/cloud/bindings/BindingsPropertiesProcessor.java b/cnb-bindings/src/main/java/org/springframework/cloud/bindings/BindingsPropertiesProcessor.java new file mode 100644 index 0000000..4c12807 --- /dev/null +++ b/cnb-bindings/src/main/java/org/springframework/cloud/bindings/BindingsPropertiesProcessor.java @@ -0,0 +1,38 @@ +/* + * 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; + +import org.springframework.lang.NonNull; + +import java.util.Map; + +/** + * An interface for types that transform the contents of a {@link Bindings} into properties for injection into a + * {@link org.springframework.core.env.PropertySource}. + */ +@FunctionalInterface +public interface BindingsPropertiesProcessor { + + /** + * Transform the currently accumulated {@link Bindings}-related properties. + * + * @param bindings the {@code Bindings} exposed to the application. + * @param properties the currently accumulated properties. + */ + void process(@NonNull Bindings bindings, @NonNull Map properties); + +} diff --git a/cnb-bindings/src/main/resources/META-INF/spring.factories b/cnb-bindings/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..f368657 --- /dev/null +++ b/cnb-bindings/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.env.EnvironmentPostProcessor=\ + org.springframework.cloud.bindings.BindingsEnvironmentPostProcessor diff --git a/cnb-bindings/src/test/java/org/springframework/cloud/cnb/core/BindingTest.java b/cnb-bindings/src/test/java/org/springframework/cloud/bindings/BindingTest.java similarity index 85% rename from cnb-bindings/src/test/java/org/springframework/cloud/cnb/core/BindingTest.java rename to cnb-bindings/src/test/java/org/springframework/cloud/bindings/BindingTest.java index f4140c7..3d14dbb 100644 --- a/cnb-bindings/src/test/java/org/springframework/cloud/cnb/core/BindingTest.java +++ b/cnb-bindings/src/test/java/org/springframework/cloud/bindings/BindingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 the original author or authors. + * 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. @@ -13,18 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.cloud.cnb.core; +package org.springframework.cloud.bindings; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.nio.file.Paths; import static org.assertj.core.api.Assertions.assertThat; +@DisplayName("Binding") final class BindingTest { @Test + @DisplayName("populates content from filesystem") void test() { Binding binding = new Binding(Paths.get("src/test/resources/test-name-1")); diff --git a/cnb-bindings/src/test/java/org/springframework/cloud/bindings/BindingsEnvironmentPostProcessorTest.java b/cnb-bindings/src/test/java/org/springframework/cloud/bindings/BindingsEnvironmentPostProcessorTest.java new file mode 100644 index 0000000..d037065 --- /dev/null +++ b/cnb-bindings/src/test/java/org/springframework/cloud/bindings/BindingsEnvironmentPostProcessorTest.java @@ -0,0 +1,116 @@ +/* + * 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; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.config.ConfigFileApplicationListener; +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.BindingsEnvironmentPostProcessor.BINDINGS_PROPERTY_SOURCE_NAME; + +@DisplayName("Bindings EnvironmentPostProcessor") +final class BindingsEnvironmentPostProcessorTest { + + private final SpringApplication application = new SpringApplication(); + + private final MockEnvironment environment = new MockEnvironment(); + + @Test + @DisplayName("does not create PropertySource if no bindings") + void noBindings() { + new BindingsEnvironmentPostProcessor(new Bindings()).postProcessEnvironment(environment, application); + + assertThat(environment.getPropertySources()).hasSize(1); + } + + @Test + @DisplayName("does not create PropertySource if no properties") + void noProperties() { + new BindingsEnvironmentPostProcessor( + new Bindings( + new Binding("test-name", Paths.get("test-path"), + Collections.emptyMap(), Collections.emptyMap()) + ) + ).postProcessEnvironment(environment, application); + + assertThat(environment.getPropertySources()).hasSize(1); + } + + @Test + @DisplayName("creates PropertySource with properties") + void containsProperties() { + 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); + + assertThat(environment.getPropertySources()).hasSize(2); + 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()) + .isLessThan(ConfigFileApplicationListener.DEFAULT_ORDER); + } +} diff --git a/cnb-bindings/src/test/java/org/springframework/cloud/bindings/BindingsTests.java b/cnb-bindings/src/test/java/org/springframework/cloud/bindings/BindingsTests.java new file mode 100644 index 0000000..e3ed083 --- /dev/null +++ b/cnb-bindings/src/test/java/org/springframework/cloud/bindings/BindingsTests.java @@ -0,0 +1,113 @@ +/* + * 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; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +@DisplayName("Bindings") +final class BindingsTests { + + @Nested + @DisplayName("Constructor") + final class Constructor { + + @Test + @DisplayName("empty if path is null") + void nullPath() { + Bindings b = new Bindings((String) null); + + assertThat(b.getBindings()).isEmpty(); + } + + @Test + @DisplayName("empty if path does not exist") + void nonExistentDirectory() { + String path = "src/test/resources/non-existent"; + Bindings b = new Bindings(path); + + assertThat(b.getBindings()).isEmpty(); + } + + @Test + @DisplayName("throws exception if path is not a directory") + void nonDirectory() throws IOException { + String path = File.createTempFile("bindings", "").getPath(); + + assertThatIllegalArgumentException().isThrownBy(() -> new Bindings(path)); + } + + @Test + @DisplayName("populates content") + void construct() { + String path = "src/test/resources"; + Bindings b = new Bindings(path); + + assertThat(b.getBindings()).hasSize(2); + } + + } + + @Nested + @DisplayName("Content") + final class Content { + + private final Bindings bindings = new Bindings( + new Binding("test-name-1", Paths.get("src/test/resources/test-name-1"), + metadata("test-kind-1", "test-provider-1"), Collections.emptyMap()), + new Binding("test-name-2", Paths.get("src/test/resources/test-name-2"), + metadata("test-kind-2", "test-provider-2"), Collections.emptyMap()) + ); + + private Map metadata(String kind, String provider) { + Map metadata = new HashMap<>(2); + metadata.put("kind", kind); + metadata.put("provider", provider); + return metadata; + } + + @Test + @DisplayName("returns content") + void getBindings() { + assertThat(bindings.getBindings()).hasSize(2); + } + + @Test + @DisplayName("filters bindings by kind") + void filterBindingsByKind() { + assertThat(bindings.filterBindings("test-kind-1", null)).hasSize(1); + } + + @Test + @DisplayName("filters bindings by provider") + void filterBindingsByProvider() { + assertThat(bindings.filterBindings(null, "test-provider-1")).hasSize(1); + } + + } + +} diff --git a/cnb-bindings/src/test/java/org/springframework/cloud/cnb/core/BindingsTests.java b/cnb-bindings/src/test/java/org/springframework/cloud/cnb/core/BindingsTests.java deleted file mode 100644 index 83f4b6a..0000000 --- a/cnb-bindings/src/test/java/org/springframework/cloud/cnb/core/BindingsTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.cnb.core; - -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -final class BindingsTests { - - @Test - void constructFromNonExistentDirectory() { - String path = "src/test/resources/non-existent"; - Bindings b = new Bindings(path); - - assertThat(b.getBindings()).isEmpty(); - } - - @Test - void constructFromNonDirectory() throws IOException { - String path = File.createTempFile("bindings", "").getPath(); - - assertThatIllegalArgumentException().isThrownBy(() -> new Bindings(path)); - } - - @Test - void construct() { - String path = "src/test/resources"; - Bindings b = new Bindings(path); - - assertThat(b.getBindings()).hasSize(2); - } - - @Test - void hasBindings() { - assertThat(Bindings.hasBindings(Collections.emptyMap())).isFalse(); - assertThat(Bindings.hasBindings(Collections.singletonMap("CNB_BINDINGS", ""))).isTrue(); - } - - @Test - void getBindings() { - String path = "src/test/resources"; - Bindings b = new Bindings(path); - - assertThat(b.getBindings()).hasSize(2); - } - - @Test - void findBindingsByKind() { - String path = "src/test/resources"; - Bindings b = new Bindings(path); - - assertThat(b.getBindings("test-kind-1", null)).hasSize(1); - } - - @Test - void findBindingsByProvider() { - String path = "src/test/resources"; - Bindings b = new Bindings(path); - - assertThat(b.getBindings(null, "test-provider-1")).hasSize(1); - } - -}