From 5fc11caefb2d37463f9c4722fb54639d305dc27e Mon Sep 17 00:00:00 2001 From: Gareth Evans Date: Tue, 26 Apr 2022 20:51:54 +0100 Subject: [PATCH] Adding support for SAP Hana bindings (#64) * Add support for SAP Hana --- README.md | 14 +++ .../SapHanaBindingsPropertiesProcessor.java | 68 +++++++++++ src/main/resources/META-INF/spring.factories | 1 + ...gSpecificEnvironmentPostProcessorTest.java | 2 +- ...apHanaBindingsPropertiesProcessorTest.java | 114 ++++++++++++++++++ 5 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/springframework/cloud/bindings/boot/SapHanaBindingsPropertiesProcessor.java create mode 100644 src/test/java/org/springframework/cloud/bindings/boot/SapHanaBindingsPropertiesProcessorTest.java diff --git a/README.md b/README.md index 49d5d21..13766c6 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,20 @@ Disable Property: `org.springframework.cloud.bindings.boot.redis.enable` | `spring.redis.ssl` | `{ssl}` | `spring.redis.url` | `{url}` +### SAP Hana +Type: `hana` +Disable Property: `org.springframework.cloud.bindings.boot.hana.enable` + +| Property | Value +| -------- | ------------------ +| `spring.datasource.driver-class-name` | `com.sap.db.jdbc.Driver` +| `spring.datasource.password` | `{password}` +| `spring.datasource.url` | `{jdbc-url}` or if not set then `jdbc:sap://{host}:{port}/{database}` +| `spring.datasource.username` | `{username}` +| `spring.r2dbc.url` | `{r2dbc-url}` or if not set then `r2dbc:sap://{host}:{port}/{database}` +| `spring.r2dbc.password` | `{password}` +| `spring.r2dbc.username` | `{username}` + ## SCS Config Server Type: `config` Disable Property: `org.springframework.cloud.bindings.boot.config.enable` diff --git a/src/main/java/org/springframework/cloud/bindings/boot/SapHanaBindingsPropertiesProcessor.java b/src/main/java/org/springframework/cloud/bindings/boot/SapHanaBindingsPropertiesProcessor.java new file mode 100644 index 0000000..9dd23ed --- /dev/null +++ b/src/main/java/org/springframework/cloud/bindings/boot/SapHanaBindingsPropertiesProcessor.java @@ -0,0 +1,68 @@ +/* + * 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.cloud.bindings.Binding; +import org.springframework.cloud.bindings.Bindings; +import org.springframework.core.env.Environment; + +import java.util.Map; + +import static org.springframework.cloud.bindings.boot.Guards.isTypeEnabled; + +/** + * An implementation of {@link BindingsPropertiesProcessor} that detects {@link Binding}s of type: {@value TYPE}. + */ +public final class SapHanaBindingsPropertiesProcessor implements BindingsPropertiesProcessor { + + /** + * The {@link Binding} type that this processor is interested in: {@value}. + **/ + public static final String TYPE = "hana"; + + @Override + public void process(Environment environment, Bindings bindings, Map properties) { + if (!isTypeEnabled(environment, TYPE)) { + return; + } + + bindings.filterBindings(TYPE).forEach(binding -> { + MapMapper map = new MapMapper(binding.getSecret(), properties); + + //jdbc properties + map.from("password").to("spring.datasource.password"); + map.from("host", "port", "database").to("spring.datasource.url", + (host, port, database) -> String.format("jdbc:sap://%s:%s/%s", host, port, database)); + map.from("username").to("spring.datasource.username"); + + // jdbcURL takes precedence + map.from("jdbc-url").to("spring.datasource.url"); + + properties.put("spring.datasource.driver-class-name", "com.sap.db.jdbc.Driver"); + + //r2dbc properties + map.from("password").to("spring.r2dbc.password"); + map.from("host", "port", "database").to("spring.r2dbc.url", + (host, port, database) -> String.format("r2dbc:sap://%s:%s/%s", host, port, database)); + map.from("username").to("spring.r2dbc.username"); + + // r2dbcURL takes precedence + map.from("r2dbc-url").to("spring.r2dbc.url"); + }); + } + +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories index 60808ad..cde7fc3 100644 --- a/src/main/resources/META-INF/spring.factories +++ b/src/main/resources/META-INF/spring.factories @@ -24,6 +24,7 @@ org.springframework.cloud.bindings.boot.BindingsPropertiesProcessor=\ org.springframework.cloud.bindings.boot.PostgreSqlBindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.RabbitMqBindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.RedisBindingsPropertiesProcessor, \ + org.springframework.cloud.bindings.boot.SapHanaBindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.SpringSecurityOAuth2BindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.SqlServerBindingsPropertiesProcessor, \ org.springframework.cloud.bindings.boot.VaultBindingsPropertiesProcessor, \ diff --git a/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java index f7686d5..03a6151 100644 --- a/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java +++ b/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java @@ -104,7 +104,7 @@ final class BindingSpecificEnvironmentPostProcessorTest { @Test @DisplayName("included implementations are registered") void includedImplementations() { - assertThat(new BindingSpecificEnvironmentPostProcessor().processors).hasSize(20); + assertThat(new BindingSpecificEnvironmentPostProcessor().processors).hasSize(21); } } diff --git a/src/test/java/org/springframework/cloud/bindings/boot/SapHanaBindingsPropertiesProcessorTest.java b/src/test/java/org/springframework/cloud/bindings/boot/SapHanaBindingsPropertiesProcessorTest.java new file mode 100644 index 0000000..394f218 --- /dev/null +++ b/src/test/java/org/springframework/cloud/bindings/boot/SapHanaBindingsPropertiesProcessorTest.java @@ -0,0 +1,114 @@ +/* + * 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.cloud.bindings.Binding; +import org.springframework.cloud.bindings.Bindings; +import org.springframework.cloud.bindings.FluentMap; +import org.springframework.mock.env.MockEnvironment; + +import java.nio.file.Paths; +import java.util.HashMap; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.bindings.boot.SapHanaBindingsPropertiesProcessor.TYPE; + +@DisplayName("SAP Hana BindingsPropertiesProcessor") +final class SapHanaBindingsPropertiesProcessorTest { + + private final FluentMap secret = new FluentMap() + .withEntry(Binding.TYPE, TYPE) + .withEntry("database", "test-database") + .withEntry("host", "test-host") + .withEntry("password", "test-password") + .withEntry("port", "test-port") + .withEntry("username", "test-username"); + + private final MockEnvironment environment = new MockEnvironment(); + + private final HashMap properties = new HashMap<>(); + + @Test + @DisplayName("composes jdbc url from host port and database") + void testJdbc() { + Bindings bindings = new Bindings( + new Binding("test-name", Paths.get("test-path"), secret) + ); + new SapHanaBindingsPropertiesProcessor().process(environment, bindings, properties); + assertThat(properties) + .containsEntry("spring.datasource.driver-class-name", "com.sap.db.jdbc.Driver") + .containsEntry("spring.datasource.password", "test-password") + .containsEntry("spring.datasource.url", "jdbc:sap://test-host:test-port/test-database") + .containsEntry("spring.datasource.username", "test-username"); + } + + @Test + @DisplayName("gives precedence to jdbc-url") + void testJdbcURL() { + Bindings bindings = new Bindings( + new Binding("test-name", Paths.get("test-path"), secret.withEntry("jdbc-url", "test-jdbc-url")) + ); + new SapHanaBindingsPropertiesProcessor().process(environment, bindings, properties); + assertThat(properties) + .containsEntry("spring.datasource.driver-class-name", "com.sap.db.jdbc.Driver") + .containsEntry("spring.datasource.password", "test-password") + .containsEntry("spring.datasource.url", "test-jdbc-url") + .containsEntry("spring.datasource.username", "test-username"); + } + + @Test + @DisplayName("composes r2dbc url from host port and database") + void testR2dbc() { + Bindings bindings = new Bindings( + new Binding("test-name", Paths.get("test-path"), secret) + ); + new SapHanaBindingsPropertiesProcessor().process(environment, bindings, properties); + assertThat(properties) + .containsEntry("spring.r2dbc.password", "test-password") + .containsEntry("spring.r2dbc.url", "r2dbc:sap://test-host:test-port/test-database") + .containsEntry("spring.r2dbc.username", "test-username"); + } + + @Test + @DisplayName("gives precedence to r2dbc-url") + void testR2dbcURL() { + Bindings bindings = new Bindings( + new Binding("test-name", Paths.get("test-path"), secret.withEntry("r2dbc-url", "test-r2dbc-url")) + ); + new SapHanaBindingsPropertiesProcessor().process(environment, bindings, properties); + assertThat(properties) + .containsEntry("spring.r2dbc.password", "test-password") + .containsEntry("spring.r2dbc.url", "test-r2dbc-url") + .containsEntry("spring.r2dbc.username", "test-username"); + } + + @Test + @DisplayName("can be disabled") + void disabled() { + Bindings bindings = new Bindings( + new Binding("test-name", Paths.get("test-path"), secret) + ); + environment.setProperty("org.springframework.cloud.bindings.boot.hana.enable", "false"); + + new SapHanaBindingsPropertiesProcessor().process(environment, bindings, properties); + + assertThat(properties).isEmpty(); + } + +}