diff --git a/README.md b/README.md
index 4f1485e..6c94216 100644
--- a/README.md
+++ b/README.md
@@ -116,6 +116,26 @@ Disable Property: `org.springframework.cloud.bindings.boot.db2.enable`
| `spring.r2dbc.password` | `{password}` |
| `spring.r2dbc.username` | `{username}` |
+### Replicated DB2 RDBMS
+Type: `db2-replicated`
+Disable Property: `org.springframework.cloud.bindings.boot.db2-replicated.enable`
+
+| Property | Value |
+| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+| `spring.datasource.replicated.rw.driver-class-name` | `com.ibm.db2.jcc.DB2Driver` |
+| `spring.datasource.replicated.rw.password` | `{rw-password}` |
+| `spring.datasource.replicated.rw.url` | `{rw-jdbc-url}` or if not set then `jdbc:db2://{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.datasource.replicated.rw.username` | `{rw-username}` |
+| `spring.r2dbc.replicated.rw.url` | `{rw-r2dbc-url}` or if not set then `r2dbc:db2://{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.r2dbc.replicated.rw.password` | `{rw-password}` |
+| `spring.r2dbc.replicated.rw.username` | `{rw-username}` |
+| `spring.datasource.replicated.ro.driver-class-name` | `com.ibm.db2.jcc.DB2Driver` |
+| `spring.datasource.replicated.ro.password` | `{ro-password}` |
+| `spring.datasource.replicated.ro.url` | `{ro-jdbc-url}` or if not set then `jdbc:db2://{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro-database set or no mapping will occur) |
+| `spring.datasource.replicated.ro.username` | `{ro-username}` |
+| `spring.r2dbc.replicated.ro.url` | `{ro-r2dbc-url}` or if not set then `r2dbc:db2://{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro- database set or no mapping will occur) |
+| `spring.r2dbc.replicated.ro.password` | `{ro-password}` |
+| `spring.r2dbc.replicated.ro.username` | `{ro-username}` |
### Elasticsearch
Type: `elasticsearch`
@@ -180,6 +200,27 @@ Disable Property: `org.springframework.cloud.bindings.boot.mysql.enable`
| `spring.r2dbc.password` | `{password}` |
| `spring.r2dbc.username` | `{username}` |
+### Replicated MySQL RDBMS
+Type: `mysql-replicated`
+Disable Property: `org.springframework.cloud.bindings.boot.mysql-replicated.enable`
+
+| Property | Value |
+| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+| `spring.datasource.replicated.rw.driver-class-name` | `org.mariadb.jdbc.Driver` or `com.mysql.cj.jdbc.Driver` depending on classpath |
+| `spring.datasource.replicated.rw.password` | `{rw-password}` |
+| `spring.datasource.replicated.rw.url` | `{rw-jdbc-url}` or if not set then `jdbc:mysql://{rw-host}:{rw-port}/{rw-database}` or `jdbc:mariadb://{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.datasource.replicated.rw.username` | `{rw-username}` |
+| `spring.r2dbc.replicated.rw.url` | `{rw-r2dbc-url}` or if not set then `r2dbc:mysql://{rw-host}:{rw-port}/{rw-database}` or `r2dbc:mariadb//{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.r2dbc.replicated.rw.password` | `{rw-password}` |
+| `spring.r2dbc.replicated.rw.username` | `{rw-username}` |
+| `spring.datasource.replicated.ro.driver-class-name` | `org.mariadb.jdbc.Driver` or `com.mysql.cj.jdbc.Driver` depending on classpath |
+| `spring.datasource.replicated.ro.password` | `{ro-password}` |
+| `spring.datasource.replicated.ro.url` | `{ro-jdbc-url}` or if not set then `jdbc:mysql://{ro-host}:{ro-port}/{ro-database}` or `jdbc:mariadb://{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro-database set or no mapping will occur) |
+| `spring.datasource.replicated.ro.username` | `{ro-username}` |
+| `spring.r2dbc.replicated.ro.url` | `{ro-r2dbc-url}` or if not set then `r2dbc:mysql://{ro-host}:{ro-port}/{ro-database}` or `r2dbc:mariadb//{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro- database set or no mapping will occur) |
+| `spring.r2dbc.replicated.ro.password` | `{ro-password}` |
+| `spring.r2dbc.replicated.ro.username` | `{ro-username}` |
+
**Note:** Libraries on the classpath are examined for the purpose of evaluating the appropriate `jdbc` and `r2dbc` URLs. The existence of both MySQL and MariaDB libraries on the classpath is not supported and may lead to non-deterministic results.
### Neo4J
@@ -208,6 +249,28 @@ Disable Property: `org.springframework.cloud.bindings.boot.oracle.enable`
| `spring.r2dbc.password` | `{password}` |
| `spring.r2dbc.username` | `{username}` |
+### Replicated Oracle RDBMS
+Type: `oracle-replicated`
+Disable Property: `org.springframework.cloud.bindings.boot.oracle-replicated.enable`
+
+| Property | Value |
+| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+| `spring.datasource.replicated.rw.driver-class-name` | `oracle.jdbc.OracleDriver` |
+| `spring.datasource.replicated.rw.password` | `{rw-password}` |
+| `spring.datasource.replicated.rw.url` | `{rw-jdbc-url}` or if not set then `jdbc:oracle://{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.datasource.replicated.rw.username` | `{rw-username}` |
+| `spring.r2dbc.replicated.rw.url` | `{rw-r2dbc-url}` or if not set then `r2dbc:oracle://{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.r2dbc.replicated.rw.password` | `{rw-password}` |
+| `spring.r2dbc.replicated.rw.username` | `{rw-username}` |
+| `spring.datasource.replicated.ro.driver-class-name` | `oracle.jdbc.OracleDriver` |
+| `spring.datasource.replicated.ro.password` | `{ro-password}` |
+| `spring.datasource.replicated.ro.url` | `{ro-jdbc-url}` or if not set then `jdbc:oracle://{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro-database set or no mapping will occur) |
+| `spring.datasource.replicated.ro.username` | `{ro-username}` |
+| `spring.r2dbc.replicated.ro.url` | `{ro-r2dbc-url}` or if not set then `r2dbc:oracle://{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro- database set or no mapping will occur) |
+| `spring.r2dbc.replicated.ro.password` | `{ro-password}` |
+| `spring.r2dbc.replicated.ro.username` | `{ro-username}` |
+
+
### PostgreSQL RDBMS
Type: `postgresql`
Disable Property: `org.springframework.cloud.bindings.boot.postgresql.enable`
@@ -222,6 +285,27 @@ Disable Property: `org.springframework.cloud.bindings.boot.postgresql.enable`
| `spring.r2dbc.password` | `{password}` |
| `spring.r2dbc.username` | `{username}` |
+### Replicated PostgreSQL RDBMS
+Type: `postgresql-replicated`
+Disable Property: `org.springframework.cloud.bindings.boot.postgresql-replicated.enable`
+
+| Property | Value |
+| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+| `spring.datasource.replicated.rw.driver-class-name` | `org.postgresql.Driver` |
+| `spring.datasource.replicated.rw.password` | `{rw-password}` |
+| `spring.datasource.replicated.rw.url` | `{rw-jdbc-url}` or if not set then `jdbc:postgresql://{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.datasource.replicated.rw.username` | `{rw-username}` |
+| `spring.r2dbc.replicated.rw.url` | `{rw-r2dbc-url}` or if not set then `r2dbc:postgresql://{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.r2dbc.replicated.rw.password` | `{rw-password}` |
+| `spring.r2dbc.replicated.rw.username` | `{rw-username}` |
+| `spring.datasource.replicated.ro.driver-class-name` | `org.postgresql.Driver` |
+| `spring.datasource.replicated.ro.password` | `{ro-password}` |
+| `spring.datasource.replicated.ro.url` | `{ro-jdbc-url}` or if not set then `jdbc:postgresql://{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro-database set or no mapping will occur) |
+| `spring.datasource.replicated.ro.username` | `{ro-username}` |
+| `spring.r2dbc.replicated.ro.url` | `{ro-r2dbc-url}` or if not set then `r2dbc:postgresql://{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro- database set or no mapping will occur) |
+| `spring.r2dbc.replicated.ro.password` | `{ro-password}` |
+| `spring.r2dbc.replicated.ro.username` | `{ro-username}` |
+
### RabbitMQ
Type: `rabbitmq`
Disable Property: `org.springframework.cloud.bindings.boot.rabbitmq.enable`
@@ -269,6 +353,27 @@ Disable Property: `org.springframework.cloud.bindings.boot.hana.enable`
| `spring.r2dbc.password` | `{password}` |
| `spring.r2dbc.username` | `{username}` |
+### Replicated SAP Hana RDBMS
+Type: `hana-replicated`
+Disable Property: `org.springframework.cloud.bindings.boot.hana-replicated.enable`
+
+| Property | Value |
+| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+| `spring.datasource.replicated.rw.driver-class-name` | `com.sap.db.jdbc.Driver` |
+| `spring.datasource.replicated.rw.password` | `{rw-password}` |
+| `spring.datasource.replicated.rw.url` | `{rw-jdbc-url}` or if not set then `jdbc:sap://{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.datasource.replicated.rw.username` | `{rw-username}` |
+| `spring.r2dbc.replicated.rw.url` | `{rw-r2dbc-url}` or if not set then `r2dbc:sap://{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.r2dbc.replicated.rw.password` | `{rw-password}` |
+| `spring.r2dbc.replicated.rw.username` | `{rw-username}` |
+| `spring.datasource.replicated.ro.driver-class-name` | `com.sap.db.jdbc.Driver` |
+| `spring.datasource.replicated.ro.password` | `{ro-password}` |
+| `spring.datasource.replicated.ro.url` | `{ro-jdbc-url}` or if not set then `jdbc:sap://{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro-database set or no mapping will occur) |
+| `spring.datasource.replicated.ro.username` | `{ro-username}` |
+| `spring.r2dbc.replicated.ro.url` | `{ro-r2dbc-url}` or if not set then `r2dbc:sap://{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro- database set or no mapping will occur) |
+| `spring.r2dbc.replicated.ro.password` | `{ro-password}` |
+| `spring.r2dbc.replicated.ro.username` | `{ro-username}` |
+
## SCS Config Server
Type: `config`
Disable Property: `org.springframework.cloud.bindings.boot.config.enable`
@@ -342,6 +447,27 @@ Disable Property: `org.springframework.cloud.bindings.boot.sqlserver.enable`
| `spring.r2dbc.password` | `{password}` |
| `spring.r2dbc.username` | `{username}` |
+### Replicated SQLServer RDBMS
+Type: `sqlserver-replicated`
+Disable Property: `org.springframework.cloud.bindings.boot.sqlserver-replicated.enable`
+
+| Property | Value |
+| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+| `spring.datasource.replicated.rw.driver-class-name` | `com.microsoft.sqlserver.jdbc.SQLServerDriver` |
+| `spring.datasource.replicated.rw.password` | `{rw-password}` |
+| `spring.datasource.replicated.rw.url` | `{rw-jdbc-url}` or if not set then `jdbc:sqlserver://{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.datasource.replicated.rw.username` | `{rw-username}` |
+| `spring.r2dbc.replicated.rw.url` | `{rw-r2dbc-url}` or if not set then `r2dbc:sqlserver://{rw-host}:{rw-port}/{rw-database}` (you must have rw-host, rw-port and rw-database set or no mapping will occur) |
+| `spring.r2dbc.replicated.rw.password` | `{rw-password}` |
+| `spring.r2dbc.replicated.rw.username` | `{rw-username}` |
+| `spring.datasource.replicated.ro.driver-class-name` | `com.microsoft.sqlserver.jdbc.SQLServerDriver` |
+| `spring.datasource.replicated.ro.password` | `{ro-password}` |
+| `spring.datasource.replicated.ro.url` | `{ro-jdbc-url}` or if not set then `jdbc:sqlserver://{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro-database set or no mapping will occur) |
+| `spring.datasource.replicated.ro.username` | `{ro-username}` |
+| `spring.r2dbc.replicated.ro.url` | `{ro-r2dbc-url}` or if not set then `r2dbc:sqlserver://{ro-host}:{ro-port}/{ro-database}` (you must have ro-host, ro-port and ro- database set or no mapping will occur) |
+| `spring.r2dbc.replicated.ro.password` | `{ro-password}` |
+| `spring.r2dbc.replicated.ro.username` | `{ro-username}` |
+
### Vault
Type: `vault`
diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/AbstractPostgreSQLBindingsPropertiesProcessor.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/AbstractPostgreSQLBindingsPropertiesProcessor.java
new file mode 100644
index 0000000..77b3a86
--- /dev/null
+++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/AbstractPostgreSQLBindingsPropertiesProcessor.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2024 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 java.nio.file.FileSystems;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.cloud.bindings.Binding;
+
+public abstract class AbstractPostgreSQLBindingsPropertiesProcessor implements BindingsPropertiesProcessor {
+
+ /**
+ * sslmode determines whether or with what priority a secure SSL TCP/IP connection will be negotiated with the server.
+ */
+ public static final String SSL_MODE = "sslmode";
+ /**
+ * sslrootcert specifies the name of a file containing SSL certificate authority (CA) certificate(s).
+ */
+ public static final String SSL_ROOT_CERT = "sslrootcert";
+ /**
+ * options Specifies command-line options to send to the server at connection start.
+ * CockroachDB uses this to pass in cluster routing id
+ */
+ public static final String OPTIONS = "options";
+
+ /**
+ * Returns a concatenated list of options parameters defined in the bound file `options` in the format specified in
+ * PostgreSQL Doc.
+ *
+ * CockroachDB, which shares the same 'postgresql://' protocol as PostgreSQL, has customized options to meet its
+ * distributed database nature.
+ * Refer to Client Connection Parameters.
+ */
+ protected String buildDbOptions(Binding binding) {
+ String options = binding.getSecret().getOrDefault(getDBOptionSecretField(), "");
+ String crdbOption = "";
+ List dbOptions = new ArrayList<>();
+ if (!options.equals("")) {
+ String[] allOpts = options.split("&");
+ for (String o : allOpts) {
+ String[] keyval = o.split("=");
+ if (keyval.length != 2 || keyval[0].length() == 0 || keyval[1].length() == 0) {
+ continue;
+ }
+ if (keyval[0].equals("--cluster")) {
+ crdbOption = keyval[0] + "=" + keyval[1];
+ } else {
+ dbOptions.add("-c " + keyval[0] + "=" + keyval[1]);
+ }
+ }
+ }
+ String combinedOptions = crdbOption;
+ if (dbOptions.size() > 0) {
+ String otherOpts = String.join(" ", dbOptions);
+ if (!combinedOptions.equals("")) {
+ combinedOptions = combinedOptions + " " + otherOpts;
+ } else {
+ combinedOptions = otherOpts;
+ }
+ }
+ if (!"".equals(combinedOptions)) {
+ combinedOptions = "options=" + combinedOptions;
+ }
+ return combinedOptions;
+ }
+
+ /**
+ * Returns a concatenated string of all ssl parameters for enabling one-way TLS (PostgreSQL certifies itself)
+ * Refer to PostgreSQL Doc
+ */
+ protected String buildSslModeParam(Binding binding) {
+ //process ssl params
+ //https://www.postgresql.org/docs/14/libpq-connect.html
+ String sslmode = binding.getSecret().getOrDefault(getSSLModeSecretField(), "");
+ String sslRootCert = binding.getSecret().getOrDefault(getSSLRootCertSecretField(), "");
+ StringBuilder sslparam = new StringBuilder();
+ if (!"".equals(sslmode)) {
+ sslparam.append(SSL_MODE).append("=").append(sslmode);
+ }
+ if (!"".equals(sslRootCert)) {
+ if (!"".equals(sslmode)) {
+ sslparam.append("&");
+ }
+ sslparam.append(SSL_ROOT_CERT).append("=")
+ .append(binding.getPath()).append(FileSystems.getDefault().getSeparator())
+ .append(sslRootCert);
+ }
+ return sslparam.toString();
+ }
+
+ /**
+ * name of the options field; override in concrete classes is field name is different
+ */
+ protected String getDBOptionSecretField()
+ {
+ return OPTIONS;
+ }
+
+ /**
+ * name of the ssl mode field; override in concrete classes is field name is different
+ */
+ protected String getSSLModeSecretField()
+ {
+ return SSL_MODE;
+ }
+
+ /**
+ * name of the ssl root cert field; override in concrete classes is field name is different
+ */
+ protected String getSSLRootCertSecretField()
+ {
+ return SSL_ROOT_CERT;
+ }
+}
diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/AbstractReplicatedDataSource.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/AbstractReplicatedDataSource.java
new file mode 100644
index 0000000..4168eb7
--- /dev/null
+++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/AbstractReplicatedDataSource.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024 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 static org.springframework.cloud.bindings.boot.Guards.isTypeEnabled;
+
+import java.util.Map;
+
+import org.springframework.cloud.bindings.Binding;
+import org.springframework.cloud.bindings.Bindings;
+import org.springframework.core.env.Environment;
+
+/**
+ * Abstract class for replicated datasources processors that implement a common properties pattern.
+ */
+public abstract class AbstractReplicatedDataSource implements BindingsPropertiesProcessor {
+
+ /**
+ * Read/write function field
+ */
+ public static final String RW_FUNCTION = "rw";
+
+ /**
+ * Read only function field
+ */
+ public static final String RO_FUNCTION = "ro";
+
+ /**
+ * Template for a base JDBC properties
+ */
+ public static final String JDBC_BASE_TEMPLATE = "spring.datasource.replicated.%s";
+
+ /**
+ * Template for a base R2DBC properties
+ */
+ public static final String R2DBC_BASE_TEMPLATE = "spring.r2dbc.replicated.%s";
+
+ /**
+ * Template for function based JDBC properties
+ */
+ public static final String JDBC_PROPERTY_TEMPLATE = "spring.datasource.replicated.%s.%s";
+
+ /**
+ * Template for function based R2DBC properties
+ */
+ public static final String R2DBC_PROPERTY_TEMPLATE = "spring.r2dbc.replicated.%s.%s";
+
+ @Override
+ public void process(Environment environment, Bindings bindings, Map properties) {
+ if (!isTypeEnabled(environment, getType())) {
+ return;
+ }
+
+ bindings.filterBindings(getType()).forEach(binding -> {
+
+ properties.put(String.format(JDBC_BASE_TEMPLATE, "name"), binding.getName());
+
+ properties.put(String.format(R2DBC_BASE_TEMPLATE, "name"), binding.getName());
+
+ final var map = new MapMapper(binding.getSecret(), properties);
+
+ buildProperties(map, properties, binding, RW_FUNCTION);
+
+ buildProperties(map, properties, binding, RO_FUNCTION);
+
+ });
+ }
+
+ /**
+ * Format a JDBC property
+ */
+ protected String formatJDBCTemplate(String fnc, String field)
+ {
+ return String.format(JDBC_PROPERTY_TEMPLATE, fnc, field);
+ }
+
+ /**
+ * Format an R2DBC property
+ */
+ protected String formatR2DBCTemplate(String fnc, String field)
+ {
+ return String.format(R2DBC_PROPERTY_TEMPLATE, fnc, field);
+ }
+
+ /**
+ * Builds the properties for a given operations function
+ * @param map The properties mapper
+ * @param properties Properties resource where new spring properties will be written to
+ * @param binding The current binding object
+ * @param fnc The operations function of the current binding
+ */
+ protected abstract void buildProperties(MapMapper map, Map properties, Binding binding, String fnc);
+
+ /**
+ * Gets the binding type of this processor.
+ * @return The binding type.
+ */
+ protected abstract String getType();
+
+}
diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/Db2ReplicatedBindingsPropertiesProcessor.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/Db2ReplicatedBindingsPropertiesProcessor.java
new file mode 100644
index 0000000..7580f73
--- /dev/null
+++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/Db2ReplicatedBindingsPropertiesProcessor.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 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 java.util.Map;
+
+import org.springframework.boot.context.event.ApplicationPreparedEvent;
+import org.springframework.boot.logging.DeferredLog;
+import org.springframework.cloud.bindings.Binding;
+import org.springframework.context.ApplicationListener;
+
+/**
+ * An implementation of {@link BindingsPropertiesProcessor} that detects {@link Binding}s of type: {@value TYPE}.
+ */
+public final class Db2ReplicatedBindingsPropertiesProcessor extends AbstractReplicatedDataSource
+ implements ApplicationListener {
+
+ /**
+ * The {@link Binding} type that this processor is interested in: {@value}.
+ **/
+ public static final String TYPE = "db2-replicated";
+
+ private static final DeferredLog LOG = new DeferredLog();
+
+ @Override
+ protected void buildProperties(MapMapper map, Map properties, Binding binding, String fnc)
+ {
+
+ //jdbc properties
+ map.from(fnc + "-username").to(formatJDBCTemplate(fnc, "username"));
+ map.from(fnc + "-password").to(formatJDBCTemplate(fnc, "password"));
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatJDBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("jdbc:db2://%s:%s/%s", host, port, database));
+
+ // jdbcURL takes precedence
+ map.from(fnc + "-jdbc-url").to(formatJDBCTemplate(fnc, "url"));
+
+ properties.put(formatJDBCTemplate(fnc, "driver-class-name"), "com.ibm.db2.jcc.DB2Driver");
+
+ //r2dbc properties
+ map.from(fnc + "-username").to(formatR2DBCTemplate(fnc, "username"));
+ map.from(fnc + "-password").to(formatR2DBCTemplate(fnc, "password"));
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatR2DBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("r2dbc:db2://%s:%s/%s", host, port, database));
+
+ // r2dbcURL takes precedence
+ map.from(fnc + "-r2dbc-url").to(formatR2DBCTemplate(fnc, "url"));
+ }
+
+ @Override
+ protected String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationPreparedEvent event) {
+ LOG.replayTo(getClass());
+ }
+}
diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/MySqlReplicatedBindingsPropertiesProcessor.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/MySqlReplicatedBindingsPropertiesProcessor.java
new file mode 100644
index 0000000..410ca1e
--- /dev/null
+++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/MySqlReplicatedBindingsPropertiesProcessor.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2024 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 java.util.Map;
+
+import org.springframework.boot.context.event.ApplicationPreparedEvent;
+import org.springframework.boot.logging.DeferredLog;
+import org.springframework.cloud.bindings.Binding;
+import org.springframework.context.ApplicationListener;
+
+/**
+ * An implementation of {@link BindingsPropertiesProcessor} that detects {@link Binding}s of type: {@value TYPE}.
+ */
+public final class MySqlReplicatedBindingsPropertiesProcessor extends AbstractReplicatedDataSource
+ implements ApplicationListener {
+
+ /**
+ * The {@link Binding} type that this processor is interested in: {@value}.
+ **/
+ public static final String TYPE = "mysql-replicated";
+
+ private static final DeferredLog LOG = new DeferredLog();
+
+ /**
+ * MySQL connection protocol constant.
+ */
+ private static final String MYSQL_PROTOCOL = "mysql";
+
+ /**
+ * MariaDB connection protocol constant.
+ */
+ private static final String MARIADB_PROTOCOL = "mariadb";
+
+ @Override
+ protected void buildProperties(MapMapper map, Map properties, Binding binding, String fnc)
+ {
+
+ //jdbc properties
+ map.from(fnc + "-username").to(formatJDBCTemplate(fnc, "username"));
+ map.from(fnc + "-password").to(formatJDBCTemplate(fnc, "password"));
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatJDBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("jdbc:%s://%s:%s/%s", evalProtocol(), host, port, database));
+
+ // jdbcURL takes precedence
+ map.from(fnc + "-jdbc-url").to(formatJDBCTemplate(fnc, "url"));
+
+ properties.put(formatJDBCTemplate(fnc, "driver-class-name"), "com.ibm.db2.jcc.DB2Driver");
+ try {
+ Class.forName("org.mariadb.jdbc.Driver", false, getClass().getClassLoader());
+ properties.put(formatJDBCTemplate(fnc, "driver-class-name"), "org.mariadb.jdbc.Driver");
+ } catch (ClassNotFoundException e) {
+ try {
+ Class.forName("com.mysql.cj.jdbc.Driver", false, getClass().getClassLoader());
+ properties.put(formatJDBCTemplate(fnc, "driver-class-name"), "com.mysql.cj.jdbc.Driver");
+ } catch (ClassNotFoundException ignored) {
+ }
+ }
+
+
+ //r2dbc properties
+ map.from(fnc + "-username").to(formatR2DBCTemplate(fnc, "username"));
+ map.from(fnc + "-password").to(formatR2DBCTemplate(fnc, "password"));
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatR2DBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("r2dbc:%s://%s:%s/%s", evalProtocol(), host, port, database));
+
+ // r2dbcURL takes precedence
+ map.from(fnc + "-r2dbc-url").to(formatR2DBCTemplate(fnc, "url"));
+ }
+
+ @Override
+ protected String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationPreparedEvent event) {
+ LOG.replayTo(getClass());
+ }
+
+ private String evalProtocol()
+ {
+ // Default to "mysql"
+ String connectionProtocol = MYSQL_PROTOCOL;
+
+ /* Starting with Spring Boot 2.7.0, the previous MySQL r2dbc driver is no longer supported and
+ * documentation suggests using the MariaDB R2DBC driver as an alternative. Some versions
+ * of the MariaDB R2DBC driver do not support "mysql" as part of the connection
+ * protocol; "mariadb" should be used instead when the MariaDB R2DBC driver class is on
+ * the classpath.
+ */
+
+ try {
+ Class.forName("org.mariadb.r2dbc.MariadbConnection");
+ connectionProtocol = MARIADB_PROTOCOL;
+ }
+ catch (ClassNotFoundException ignored) {
+ }
+
+ return connectionProtocol;
+ }
+}
diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/OracleReplicatedBindingsPropertiesProcessor.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/OracleReplicatedBindingsPropertiesProcessor.java
new file mode 100644
index 0000000..c823963
--- /dev/null
+++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/OracleReplicatedBindingsPropertiesProcessor.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2024 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 java.util.Map;
+
+import org.springframework.boot.context.event.ApplicationPreparedEvent;
+import org.springframework.boot.logging.DeferredLog;
+import org.springframework.cloud.bindings.Binding;
+import org.springframework.context.ApplicationListener;
+
+/**
+ * An implementation of {@link BindingsPropertiesProcessor} that detects {@link Binding}s of type: {@value TYPE}.
+ */
+public final class OracleReplicatedBindingsPropertiesProcessor extends AbstractReplicatedDataSource
+ implements ApplicationListener {
+
+ /**
+ * The {@link Binding} type that this processor is interested in: {@value}.
+ **/
+ public static final String TYPE = "oracle-replicated";
+
+ private static final DeferredLog LOG = new DeferredLog();
+
+ @Override
+ protected void buildProperties(MapMapper map, Map properties, Binding binding, String fnc)
+ {
+
+ //jdbc properties
+ map.from(fnc + "-username").to(formatJDBCTemplate(fnc, "username"));
+ map.from(fnc + "-password").to(formatJDBCTemplate(fnc, "password"));
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatJDBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("jdbc:oracle://%s:%s/%s", host, port, database));
+
+ // jdbcURL takes precedence
+ map.from(fnc + "-jdbc-url").to(formatJDBCTemplate(fnc, "url"));
+
+ properties.put(formatJDBCTemplate(fnc, "driver-class-name"), "oracle.jdbc.OracleDriver");
+
+ //r2dbc properties
+ map.from(fnc + "-username").to(formatR2DBCTemplate(fnc, "username"));
+ map.from(fnc + "-password").to(formatR2DBCTemplate(fnc, "password"));
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatR2DBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("r2dbc:oracle://%s:%s/%s", host, port, database));
+
+ // r2dbcURL takes precedence
+ map.from(fnc + "-r2dbc-url").to(formatR2DBCTemplate(fnc, "url"));
+ }
+
+ @Override
+ protected String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationPreparedEvent event) {
+ LOG.replayTo(getClass());
+ }
+
+}
diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/PostgreSqlBindingsPropertiesProcessor.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/PostgreSqlBindingsPropertiesProcessor.java
index 4cdd783..6a5b8f5 100644
--- a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/PostgreSqlBindingsPropertiesProcessor.java
+++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/PostgreSqlBindingsPropertiesProcessor.java
@@ -20,9 +20,6 @@
import org.springframework.cloud.bindings.Bindings;
import org.springframework.core.env.Environment;
-import java.nio.file.FileSystems;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Map;
import static org.springframework.cloud.bindings.boot.Guards.isTypeEnabled;
@@ -32,25 +29,13 @@
*
* @see JDBC URL Format
*/
-public final class PostgreSqlBindingsPropertiesProcessor implements BindingsPropertiesProcessor {
+public final class PostgreSqlBindingsPropertiesProcessor extends AbstractPostgreSQLBindingsPropertiesProcessor {
/**
* The {@link Binding} type that this processor is interested in: {@value}.
**/
public static final String TYPE = "postgresql";
- /**
- * sslmode determines whether or with what priority a secure SSL TCP/IP connection will be negotiated with the server.
- */
- public static final String SSL_MODE = "sslmode";
- /**
- * sslrootcert specifies the name of a file containing SSL certificate authority (CA) certificate(s).
- */
- public static final String SSL_ROOT_CERT = "sslrootcert";
- /**
- * options Specifies command-line options to send to the server at connection start.
- * CockroachDB uses this to pass in cluster routing id
- */
- public static final String OPTIONS = "options";
+
public static final String SPRING_DATASOURCE_URL = "spring.datasource.url";
public static final String SPRING_R2DBC_URL = "spring.r2dbc.url";
@@ -99,69 +84,4 @@ public void process(Environment environment, Bindings bindings, MapPostgreSQL Doc.
- *
- * CockroachDB, which shares the same 'postgresql://' protocol as PostgreSQL, has customized options to meet its
- * distributed database nature.
- * Refer to Client Connection Parameters.
- */
- private String buildDbOptions(Binding binding) {
- String options = binding.getSecret().getOrDefault(OPTIONS, "");
- String crdbOption = "";
- List dbOptions = new ArrayList<>();
- if (!options.equals("")) {
- String[] allOpts = options.split("&");
- for (String o : allOpts) {
- String[] keyval = o.split("=");
- if (keyval.length != 2 || keyval[0].length() == 0 || keyval[1].length() == 0) {
- continue;
- }
- if (keyval[0].equals("--cluster")) {
- crdbOption = keyval[0] + "=" + keyval[1];
- } else {
- dbOptions.add("-c " + keyval[0] + "=" + keyval[1]);
- }
- }
- }
- String combinedOptions = crdbOption;
- if (dbOptions.size() > 0) {
- String otherOpts = String.join(" ", dbOptions);
- if (!combinedOptions.equals("")) {
- combinedOptions = combinedOptions + " " + otherOpts;
- } else {
- combinedOptions = otherOpts;
- }
- }
- if (!"".equals(combinedOptions)) {
- combinedOptions = "options=" + combinedOptions;
- }
- return combinedOptions;
- }
-
- /**
- * Returns a concatenated string of all ssl parameters for enabling one-way TLS (PostgreSQL certifies itself)
- * Refer to PostgreSQL Doc
- */
- private String buildSslModeParam(Binding binding) {
- //process ssl params
- //https://www.postgresql.org/docs/14/libpq-connect.html
- String sslmode = binding.getSecret().getOrDefault(SSL_MODE, "");
- String sslRootCert = binding.getSecret().getOrDefault(SSL_ROOT_CERT, "");
- StringBuilder sslparam = new StringBuilder();
- if (!"".equals(sslmode)) {
- sslparam.append(SSL_MODE).append("=").append(sslmode);
- }
- if (!"".equals(sslRootCert)) {
- if (!"".equals(sslmode)) {
- sslparam.append("&");
- }
- sslparam.append(SSL_ROOT_CERT).append("=")
- .append(binding.getPath()).append(FileSystems.getDefault().getSeparator())
- .append(sslRootCert);
- }
- return sslparam.toString();
- }
}
diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/PostgreSqlReplicatedBindingsPropertiesProcessor.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/PostgreSqlReplicatedBindingsPropertiesProcessor.java
new file mode 100644
index 0000000..fbf3b2e
--- /dev/null
+++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/PostgreSqlReplicatedBindingsPropertiesProcessor.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2024 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.boot.context.event.ApplicationPreparedEvent;
+import org.springframework.boot.logging.DeferredLog;
+import org.springframework.cloud.bindings.Binding;
+import org.springframework.cloud.bindings.Bindings;
+import org.springframework.context.ApplicationListener;
+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 PostgreSqlReplicatedBindingsPropertiesProcessor extends AbstractPostgreSQLBindingsPropertiesProcessor
+ implements BindingsPropertiesProcessor, ApplicationListener {
+
+ /**
+ * The {@link Binding} type that this processor is interested in: {@value}.
+ **/
+ public static final String TYPE = "postgresql-replicated";
+
+ private static final DeferredLog LOG = new DeferredLog();
+
+ /**
+ * Read/write function field
+ */
+ private static final String RW_FUNCTION = "rw";
+
+ /**
+ * Read only function field
+ */
+ private static final String RO_FUNCTION = "ro";
+
+ /**
+ * Template for a base JDBC properties
+ */
+ private static final String JDBC_BASE_TEMPLATE = "spring.datasource.replicated.%s";
+
+ /**
+ * Template for a base R2DBC properties
+ */
+ private static final String R2DBC_BASE_TEMPLATE = "spring.r2dbc.replicated.%s";
+
+ /**
+ * Template for function based JDBC properties
+ */
+ private static final String JDBC_PROPERTY_TEMPLATE = "spring.datasource.replicated.%s.%s";
+
+ /**
+ * Template for function based R2DBC properties
+ */
+ private static final String R2DBC_PROPERTY_TEMPLATE = "spring.r2dbc.replicated.%s.%s";
+
+ private String mode = RW_FUNCTION;
+
+ @Override
+ public void process(Environment environment, Bindings bindings, Map properties) {
+
+ if (!isTypeEnabled(environment, TYPE)) {
+ return;
+ }
+
+ bindings.filterBindings(TYPE).forEach(binding -> {
+
+ properties.put(String.format(JDBC_BASE_TEMPLATE, "name"), binding.getName());
+
+ properties.put(String.format(R2DBC_BASE_TEMPLATE, "name"), binding.getName());
+
+ final var map = new MapMapper(binding.getSecret(), properties);
+
+ mode = RW_FUNCTION;
+ buildProperties(map, properties, binding, RW_FUNCTION);
+
+ mode = RO_FUNCTION;
+ buildProperties(map, properties, binding, RO_FUNCTION);
+
+ });
+ }
+
+ private void buildProperties(MapMapper map, Map properties, Binding binding, String fnc)
+ {
+ map.from(fnc + "-password").to(formatJDBCTemplate(fnc, "password"));
+
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatJDBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("jdbc:postgresql://%s:%s/%s", host, port, database));
+
+ String sslParam = buildSslModeParam(binding);
+ String sslModeOptions = buildDbOptions(binding);
+ if (!"".equals(sslParam) && !"".equals(sslModeOptions)) {
+ sslModeOptions = sslParam + "&" + sslModeOptions;
+ } else if (!"".equals(sslParam) ) {
+ sslModeOptions = sslParam;
+ }
+
+ if (!"".equals(sslModeOptions)) {
+ properties.put(formatJDBCTemplate(fnc, "url") ,
+ properties.get(formatJDBCTemplate(fnc, "url")) + "?" + sslModeOptions);
+ }
+ map.from(fnc + "-username").to(formatJDBCTemplate(fnc, "username"));
+
+ // jdbcURL takes precedence
+ map.from(fnc + "-jdbc-url").to(formatJDBCTemplate(fnc, "url"));
+
+ properties.put(formatJDBCTemplate(fnc, "driver-class-name"), "org.postgresql.Driver");
+
+ //r2dbc properties
+ map.from(fnc + "-password").to(formatR2DBCTemplate(fnc, "password"));
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatR2DBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("r2dbc:postgresql://%s:%s/%s", host, port, database));
+ if (!"".equals(sslModeOptions)) {
+ properties.put(formatR2DBCTemplate(fnc, "url") ,
+ properties.get(formatR2DBCTemplate(fnc, "url")) + "?" + sslModeOptions);
+ }
+ map.from(fnc + "-username").to(formatR2DBCTemplate(fnc, "username"));
+
+ // r2dbcURL takes precedence
+ map.from(fnc + "-r2dbc-url").to(formatR2DBCTemplate(fnc, "url"));
+ }
+
+ private String formatJDBCTemplate(String fnc, String field)
+ {
+ return String.format(JDBC_PROPERTY_TEMPLATE, fnc, field);
+ }
+
+ private String formatR2DBCTemplate(String fnc, String field)
+ {
+ return String.format(R2DBC_PROPERTY_TEMPLATE, fnc, field);
+ }
+
+ @Override
+ protected String getDBOptionSecretField()
+ {
+ return String.format("%s-%s", mode , OPTIONS);
+ }
+
+ @Override
+ protected String getSSLModeSecretField()
+ {
+ return String.format("%s-%s", mode , SSL_MODE);
+ }
+
+ @Override
+ protected String getSSLRootCertSecretField()
+ {
+ return String.format("%s-%s", mode , SSL_ROOT_CERT);
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationPreparedEvent event) {
+ LOG.replayTo(getClass());
+ }
+}
diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/SapHanaReplicatedBindingsPropertiesProcessor.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/SapHanaReplicatedBindingsPropertiesProcessor.java
new file mode 100644
index 0000000..b2bce25
--- /dev/null
+++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/SapHanaReplicatedBindingsPropertiesProcessor.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024 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 java.util.Map;
+
+import org.springframework.boot.context.event.ApplicationPreparedEvent;
+import org.springframework.boot.logging.DeferredLog;
+import org.springframework.cloud.bindings.Binding;
+import org.springframework.context.ApplicationListener;
+
+/**
+ * An implementation of {@link BindingsPropertiesProcessor} that detects {@link Binding}s of type: {@value TYPE}.
+ */
+public final class SapHanaReplicatedBindingsPropertiesProcessor extends AbstractReplicatedDataSource
+ implements ApplicationListener {
+
+ /**
+ * The {@link Binding} type that this processor is interested in: {@value}.
+ **/
+ public static final String TYPE = "hana-replicated";
+
+ private static final DeferredLog LOG = new DeferredLog();
+
+ @Override
+ protected void buildProperties(MapMapper map, Map properties, Binding binding, String fnc)
+ {
+ //jdbc properties
+ map.from(fnc + "-username").to(formatJDBCTemplate(fnc, "username"));
+ map.from(fnc + "-password").to(formatJDBCTemplate(fnc, "password"));
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatJDBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("jdbc:sap://%s:%s/%s", host, port, database));
+
+ // jdbcURL takes precedence
+ map.from(fnc + "-jdbc-url").to(formatJDBCTemplate(fnc, "url"));
+
+ properties.put(formatJDBCTemplate(fnc, "driver-class-name"), "com.sap.db.jdbc.Driver");
+
+ //r2dbc properties
+ map.from(fnc + "-username").to(formatR2DBCTemplate(fnc, "username"));
+ map.from(fnc + "-password").to(formatR2DBCTemplate(fnc, "password"));
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatR2DBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("r2dbc:sap://%s:%s/%s", host, port, database));
+
+ // r2dbcURL takes precedence
+ map.from(fnc + "-r2dbc-url").to(formatR2DBCTemplate(fnc, "url"));
+ }
+
+ @Override
+ protected String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationPreparedEvent event) {
+ LOG.replayTo(getClass());
+ }
+}
diff --git a/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/SqlServerReplicatedBindingsPropertiesProcessor.java b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/SqlServerReplicatedBindingsPropertiesProcessor.java
new file mode 100644
index 0000000..d096bf8
--- /dev/null
+++ b/spring-cloud-bindings/src/main/java/org/springframework/cloud/bindings/boot/SqlServerReplicatedBindingsPropertiesProcessor.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024 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 java.util.Map;
+
+import org.springframework.boot.context.event.ApplicationPreparedEvent;
+import org.springframework.boot.logging.DeferredLog;
+import org.springframework.cloud.bindings.Binding;
+import org.springframework.context.ApplicationListener;
+
+/**
+ * An implementation of {@link BindingsPropertiesProcessor} that detects {@link Binding}s of type: {@value TYPE}.
+ */
+public final class SqlServerReplicatedBindingsPropertiesProcessor extends AbstractReplicatedDataSource
+ implements ApplicationListener {
+
+ /**
+ * The {@link Binding} type that this processor is interested in: {@value}.
+ **/
+ public static final String TYPE = "sqlserver-replicated";
+
+ private static final DeferredLog LOG = new DeferredLog();
+
+ @Override
+ protected void buildProperties(MapMapper map, Map properties, Binding binding, String fnc)
+ {
+ //jdbc properties
+ map.from(fnc + "-username").to(formatJDBCTemplate(fnc, "username"));
+ map.from(fnc + "-password").to(formatJDBCTemplate(fnc, "password"));
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatJDBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("jdbc:sqlserver://%s:%s/%s", host, port, database));
+
+ // jdbcURL takes precedence
+ map.from(fnc + "-jdbc-url").to(formatJDBCTemplate(fnc, "url"));
+
+ properties.put(formatJDBCTemplate(fnc, "driver-class-name"), "com.microsoft.sqlserver.jdbc.SQLServerDriver");
+
+ //r2dbc properties
+ map.from(fnc + "-username").to(formatR2DBCTemplate(fnc, "username"));
+ map.from(fnc + "-password").to(formatR2DBCTemplate(fnc, "password"));
+ map.from(fnc + "-host", fnc + "-port", fnc + "-database").to(formatR2DBCTemplate(fnc, "url"),
+ (host, port, database) -> String.format("r2dbc:sqlserver://%s:%s/%s", host, port, database));
+
+ // r2dbcURL takes precedence
+ map.from(fnc + "-r2dbc-url").to(formatR2DBCTemplate(fnc, "url"));
+ }
+
+ @Override
+ protected String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationPreparedEvent event) {
+ LOG.replayTo(getClass());
+ }
+}
diff --git a/spring-cloud-bindings/src/main/resources/META-INF/spring.factories b/spring-cloud-bindings/src/main/resources/META-INF/spring.factories
index cde7fc3..1f60ac9 100644
--- a/spring-cloud-bindings/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-bindings/src/main/resources/META-INF/spring.factories
@@ -1,7 +1,13 @@
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bindings.boot.BindingFlattenedEnvironmentPostProcessor, \
org.springframework.cloud.bindings.boot.BindingSpecificEnvironmentPostProcessor, \
+ org.springframework.cloud.bindings.boot.Db2ReplicatedBindingsPropertiesProcessor, \
+ org.springframework.cloud.bindings.boot.MySqlReplicatedBindingsPropertiesProcessor, \
+ org.springframework.cloud.bindings.boot.OracleReplicatedBindingsPropertiesProcessor, \
+ org.springframework.cloud.bindings.boot.PostgreSqlReplicatedBindingsPropertiesProcessor, \
+ org.springframework.cloud.bindings.boot.SapHanaReplicatedBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.SpringSecurityOAuth2BindingsPropertiesProcessor, \
+ org.springframework.cloud.bindings.boot.SqlServerReplicatedBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.VaultBindingsPropertiesProcessor
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.bindings.boot.BindingFlattenedEnvironmentPostProcessor, \
@@ -13,19 +19,25 @@ org.springframework.cloud.bindings.boot.BindingsPropertiesProcessor=\
org.springframework.cloud.bindings.boot.ConfigServerBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.CouchbaseBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.Db2BindingsPropertiesProcessor, \
+ org.springframework.cloud.bindings.boot.Db2ReplicatedBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.ElasticsearchBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.EurekaBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.KafkaBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.LDAPBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.MongoDbBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.MySqlBindingsPropertiesProcessor, \
+ org.springframework.cloud.bindings.boot.MySqlReplicatedBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.Neo4JBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.OracleBindingsPropertiesProcessor, \
+ org.springframework.cloud.bindings.boot.OracleReplicatedBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.PostgreSqlBindingsPropertiesProcessor, \
+ org.springframework.cloud.bindings.boot.PostgreSqlReplicatedBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.RabbitMqBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.RedisBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.SapHanaBindingsPropertiesProcessor, \
+ org.springframework.cloud.bindings.boot.SapHanaReplicatedBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.SpringSecurityOAuth2BindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.SqlServerBindingsPropertiesProcessor, \
+ org.springframework.cloud.bindings.boot.SqlServerReplicatedBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.VaultBindingsPropertiesProcessor, \
org.springframework.cloud.bindings.boot.WavefrontBindingsPropertiesProcessor
diff --git a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java
index 85a7d7b..4646db8 100644
--- a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java
+++ b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/BindingSpecificEnvironmentPostProcessorTest.java
@@ -104,7 +104,7 @@ void order() {
@Test
@DisplayName("included implementations are registered")
void includedImplementations() {
- assertThat(new BindingSpecificEnvironmentPostProcessor().processors).hasSize(21);
+ assertThat(new BindingSpecificEnvironmentPostProcessor().processors).hasSize(27);
}
}
diff --git a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/Db2ReplicatedBindingsPropertiesProcessorTest.java b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/Db2ReplicatedBindingsPropertiesProcessorTest.java
new file mode 100644
index 0000000..e830ac7
--- /dev/null
+++ b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/Db2ReplicatedBindingsPropertiesProcessorTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2024 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 static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.cloud.bindings.boot.Db2ReplicatedBindingsPropertiesProcessor.TYPE;
+
+import java.nio.file.Paths;
+import java.util.HashMap;
+
+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;
+
+@DisplayName("DB2Replicated BindingsPropertiesProcessor")
+public class Db2ReplicatedBindingsPropertiesProcessorTest {
+
+ private final FluentMap secret = new FluentMap()
+ .withEntry(Binding.TYPE, TYPE)
+ .withEntry("rw-database", "testrw-database")
+ .withEntry("rw-host", "testrw-host")
+ .withEntry("rw-password", "testrw-password")
+ .withEntry("rw-port", "testrw-port")
+ .withEntry("rw-username", "testrw-username")
+ .withEntry("ro-database", "testro-database")
+ .withEntry("ro-host", "testro-host")
+ .withEntry("ro-password", "testro-password")
+ .withEntry("ro-port", "testro-port")
+ .withEntry("ro-username", "testro-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("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new Db2ReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "com.ibm.db2.jcc.DB2Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:db2://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "com.ibm.db2.jcc.DB2Driver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "jdbc:db2://testro-host:testro-port/testro-database")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("gives precedence to jdbc-url")
+ void testJdbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-jdbc-url", "testrw-jdbc-url").withEntry("ro-jdbc-url", "testro-jdbc-url"))
+ );
+ new Db2ReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "com.ibm.db2.jcc.DB2Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "testrw-jdbc-url")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "com.ibm.db2.jcc.DB2Driver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "testro-jdbc-url")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("contributes r2dbc properties")
+ void testR2dbc() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new Db2ReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "r2dbc:db2://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "r2dbc:db2://testro-host:testro-port/testro-database")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("gives precedence to r2dbc-url")
+ void testR2dbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-r2dbc-url", "testrw-r2dbc-url").withEntry("ro-r2dbc-url", "testro-r2dbc-url"))
+ );
+ new Db2ReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "testrw-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "testro-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("can be disabled")
+ void disabled() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ environment.setProperty("org.springframework.cloud.bindings.boot.db2-replicated.enable", "false");
+
+ new Db2ReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+
+ assertThat(properties).isEmpty();
+ }
+}
diff --git a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/MySqlReplicatedBindingsPropertiesProcessorTest.java b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/MySqlReplicatedBindingsPropertiesProcessorTest.java
new file mode 100644
index 0000000..8573231
--- /dev/null
+++ b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/MySqlReplicatedBindingsPropertiesProcessorTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2024 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 static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.cloud.bindings.boot.MySqlReplicatedBindingsPropertiesProcessor.TYPE;
+
+import java.nio.file.Paths;
+import java.util.HashMap;
+
+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;
+
+@DisplayName("MySQLReplicated BindingsPropertiesProcessor")
+public class MySqlReplicatedBindingsPropertiesProcessorTest {
+
+ private final FluentMap secret = new FluentMap()
+ .withEntry(Binding.TYPE, TYPE)
+ .withEntry("rw-database", "testrw-database")
+ .withEntry("rw-host", "testrw-host")
+ .withEntry("rw-password", "testrw-password")
+ .withEntry("rw-port", "testrw-port")
+ .withEntry("rw-username", "testrw-username")
+ .withEntry("ro-database", "testro-database")
+ .withEntry("ro-host", "testro-host")
+ .withEntry("ro-password", "testro-password")
+ .withEntry("ro-port", "testro-port")
+ .withEntry("ro-username", "testro-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("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new MySqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "org.mariadb.jdbc.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:mariadb://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "org.mariadb.jdbc.Driver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "jdbc:mariadb://testro-host:testro-port/testro-database")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("gives precedence to jdbc-url")
+ void testJdbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-jdbc-url", "testrw-jdbc-url").withEntry("ro-jdbc-url", "testro-jdbc-url"))
+ );
+ new MySqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "org.mariadb.jdbc.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "testrw-jdbc-url")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "org.mariadb.jdbc.Driver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "testro-jdbc-url")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("composes r2dbc url from host port and database")
+ void testR2dbc() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new MySqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "r2dbc:mariadb://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "r2dbc:mariadb://testro-host:testro-port/testro-database")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("gives precedence to r2dbc-url")
+ void testR2dbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-r2dbc-url", "testrw-r2dbc-url").withEntry("ro-r2dbc-url", "testro-r2dbc-url"))
+ );
+ new MySqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "testrw-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "testro-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("can be disabled")
+ void disabled() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ environment.setProperty("org.springframework.cloud.bindings.boot.mysql-replicated.enable", "false");
+
+ new MySqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+
+ assertThat(properties).isEmpty();
+ }
+}
diff --git a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/OracleReplicatedBindingsPropertiesProcessorTest.java b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/OracleReplicatedBindingsPropertiesProcessorTest.java
new file mode 100644
index 0000000..ec9a3dc
--- /dev/null
+++ b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/OracleReplicatedBindingsPropertiesProcessorTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2024 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 static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.cloud.bindings.boot.OracleReplicatedBindingsPropertiesProcessor.TYPE;
+
+import java.nio.file.Paths;
+import java.util.HashMap;
+
+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;
+
+@DisplayName("OracleReplicated BindingsPropertiesProcessor")
+public class OracleReplicatedBindingsPropertiesProcessorTest {
+ private final FluentMap secret = new FluentMap()
+ .withEntry(Binding.TYPE, TYPE)
+ .withEntry("rw-database", "testrw-database")
+ .withEntry("rw-host", "testrw-host")
+ .withEntry("rw-password", "testrw-password")
+ .withEntry("rw-port", "testrw-port")
+ .withEntry("rw-username", "testrw-username")
+ .withEntry("ro-database", "testro-database")
+ .withEntry("ro-host", "testro-host")
+ .withEntry("ro-password", "testro-password")
+ .withEntry("ro-port", "testro-port")
+ .withEntry("ro-username", "testro-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("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new OracleReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "oracle.jdbc.OracleDriver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:oracle://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "oracle.jdbc.OracleDriver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "jdbc:oracle://testro-host:testro-port/testro-database")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("gives precedence to jdbc-url")
+ void testJdbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-jdbc-url", "testrw-jdbc-url").withEntry("ro-jdbc-url", "testro-jdbc-url"))
+ );
+ new OracleReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "oracle.jdbc.OracleDriver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "testrw-jdbc-url")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "oracle.jdbc.OracleDriver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "testro-jdbc-url")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("composes r2dbc url from host port and database")
+ void testR2dbc() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new OracleReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "r2dbc:oracle://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "r2dbc:oracle://testro-host:testro-port/testro-database")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("gives precedence to r2dbc-url")
+ void testR2dbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-r2dbc-url", "testrw-r2dbc-url").withEntry("ro-r2dbc-url", "testro-r2dbc-url"))
+ );
+ new OracleReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "testrw-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "testro-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("can be disabled")
+ void disabled() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ environment.setProperty("org.springframework.cloud.bindings.boot.oracle-replicated.enable", "false");
+
+ new OracleReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+
+ assertThat(properties).isEmpty();
+ }
+}
diff --git a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/PostgreSqlReplicatedBindingsPropertiesProcessorTest.java b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/PostgreSqlReplicatedBindingsPropertiesProcessorTest.java
new file mode 100644
index 0000000..af71f84
--- /dev/null
+++ b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/PostgreSqlReplicatedBindingsPropertiesProcessorTest.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2024 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 static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.cloud.bindings.boot.PostgreSqlReplicatedBindingsPropertiesProcessor.TYPE;
+
+import java.nio.file.Paths;
+import java.util.HashMap;
+
+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;
+
+@DisplayName("PostgreSQLReplicated BindingsPropertiesProcessor")
+public class PostgreSqlReplicatedBindingsPropertiesProcessorTest {
+
+ private final FluentMap secret = new FluentMap()
+ .withEntry(Binding.TYPE, TYPE)
+ .withEntry("rw-database", "testrw-database")
+ .withEntry("rw-host", "testrw-host")
+ .withEntry("rw-password", "testrw-password")
+ .withEntry("rw-port", "testrw-port")
+ .withEntry("rw-username", "testrw-username")
+ .withEntry("ro-database", "testro-database")
+ .withEntry("ro-host", "testro-host")
+ .withEntry("ro-password", "testro-password")
+ .withEntry("ro-port", "testro-port")
+ .withEntry("ro-username", "testro-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("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "org.postgresql.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:postgresql://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "org.postgresql.Driver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "jdbc:postgresql://testro-host:testro-port/testro-database")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+
+ }
+
+
+ @Test
+ @DisplayName("gives precedence to jdbc-url")
+ void testJdbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-jdbc-url", "testrw-jdbc-url").withEntry("ro-jdbc-url", "testro-jdbc-url"))
+ );
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "org.postgresql.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "testrw-jdbc-url")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "org.postgresql.Driver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "testro-jdbc-url")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("composes r2dbc url from host port and database")
+ void testR2dbc() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "r2dbc:postgresql://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "r2dbc:postgresql://testro-host:testro-port/testro-database")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("gives precedence to r2dbc-url")
+ void testR2dbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-r2dbc-url", "testrw-r2dbc-url").withEntry("ro-r2dbc-url", "testro-r2dbc-url"))
+ );
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "testrw-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "testro-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+
+ @Test
+ @DisplayName("can be disabled")
+ void disabled() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ environment.setProperty("org.springframework.cloud.bindings.boot.postgresql-replicated.enable", "false");
+
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+
+ assertThat(properties).isEmpty();
+ }
+
+ @Test
+ @DisplayName("composes jdbc url from host port and database with sslmode and crdb option")
+ void testJdbcWithSsl() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-sslmode", "verify-full")
+ .withEntry("rw-sslrootcert", "root.cert")
+ .withEntry("rw-options", "--cluster=routing-id&opt=val1"))
+ );
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "org.postgresql.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:postgresql://testrw-host:testrw-port/testrw-database?sslmode=verify-full&sslrootcert=dbrw-path/root.cert&options=--cluster=routing-id -c opt=val1")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("composes jdbc url from host port and database with sslmode and crdb option")
+ void testJdbcWithInvalidCrdbOption() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-sslmode", "verify-full")
+ .withEntry("rw-sslrootcert", "root.cert")
+ .withEntry("rw-options", "-cluster=routing-id&opt=val1"))
+ );
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "org.postgresql.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:postgresql://testrw-host:testrw-port/testrw-database?sslmode=verify-full&sslrootcert=dbrw-path/root.cert&options=-c -cluster=routing-id -c opt=val1")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("composes r2dbc url from host port and database with sslmode and crdb option")
+ void testR2dbcWithSsl() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-sslmode", "verify-full")
+ .withEntry("rw-sslrootcert", "root.cert")
+ .withEntry("rw-options", "--cluster=routing-id&opt=val1"))
+ );
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "r2dbc:postgresql://testrw-host:testrw-port/testrw-database?sslmode=verify-full&sslrootcert=dbrw-path/root.cert&options=--cluster=routing-id -c opt=val1")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("composes jdbc url from host port and database with sslmode disable")
+ void testJdbcWithSslDisable() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-sslmode", "disable"))
+ );
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "org.postgresql.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:postgresql://testrw-host:testrw-port/testrw-database?sslmode=disable")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("composes jdbc url from host port and database with DB options")
+ void testJdbcWithDBoptions() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-options", "opt1=val1&opt2=val2"))
+ );
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "org.postgresql.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:postgresql://testrw-host:testrw-port/testrw-database?options=-c opt1=val1 -c opt2=val2")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("composes jdbc url from host port and database with invalid DB options")
+ void testJdbcWithInvaildDBoptions() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-options", "opt1=val1&opt"))
+ );
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "org.postgresql.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:postgresql://testrw-host:testrw-port/testrw-database?options=-c opt1=val1")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("composes jdbc url from host port and database with empty DB options")
+ void testJdbcWithEmptyDBoptions() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-options", ""))
+ );
+ new PostgreSqlReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "org.postgresql.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:postgresql://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+}
diff --git a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/SapHanaReplicatedBindingsPropertiesProcessorTest.java b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/SapHanaReplicatedBindingsPropertiesProcessorTest.java
new file mode 100644
index 0000000..1c6e2ce
--- /dev/null
+++ b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/SapHanaReplicatedBindingsPropertiesProcessorTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2024 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 static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.cloud.bindings.boot.SapHanaReplicatedBindingsPropertiesProcessor.TYPE;
+
+import java.nio.file.Paths;
+import java.util.HashMap;
+
+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;
+
+@DisplayName("SAP HanaRepicated BindingsPropertiesProcessor")
+public class SapHanaReplicatedBindingsPropertiesProcessorTest {
+
+ private final FluentMap secret = new FluentMap()
+ .withEntry(Binding.TYPE, TYPE)
+ .withEntry("rw-database", "testrw-database")
+ .withEntry("rw-host", "testrw-host")
+ .withEntry("rw-password", "testrw-password")
+ .withEntry("rw-port", "testrw-port")
+ .withEntry("rw-username", "testrw-username")
+ .withEntry("ro-database", "testro-database")
+ .withEntry("ro-host", "testro-host")
+ .withEntry("ro-password", "testro-password")
+ .withEntry("ro-port", "testro-port")
+ .withEntry("ro-username", "testro-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("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new SapHanaReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "com.sap.db.jdbc.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:sap://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "com.sap.db.jdbc.Driver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "jdbc:sap://testro-host:testro-port/testro-database")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("gives precedence to jdbc-url")
+ void testJdbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-jdbc-url", "testrw-jdbc-url").withEntry("ro-jdbc-url", "testro-jdbc-url"))
+ );
+ new SapHanaReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "com.sap.db.jdbc.Driver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "testrw-jdbc-url")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "com.sap.db.jdbc.Driver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "testro-jdbc-url")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("composes r2dbc url from host port and database")
+ void testR2dbc() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new SapHanaReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "r2dbc:sap://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "r2dbc:sap://testro-host:testro-port/testro-database")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("gives precedence to r2dbc-url")
+ void testR2dbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-r2dbc-url", "testrw-r2dbc-url").withEntry("ro-r2dbc-url", "testro-r2dbc-url"))
+ );
+ new SapHanaReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "testrw-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "testro-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("can be disabled")
+ void disabled() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ environment.setProperty("org.springframework.cloud.bindings.boot.hana-replicated.enable", "false");
+
+ new SapHanaReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+
+ assertThat(properties).isEmpty();
+ }
+}
diff --git a/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/SqlServerReplicatedBindingsPropertiesProcessorTest.java b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/SqlServerReplicatedBindingsPropertiesProcessorTest.java
new file mode 100644
index 0000000..287a65c
--- /dev/null
+++ b/spring-cloud-bindings/src/test/java/org/springframework/cloud/bindings/boot/SqlServerReplicatedBindingsPropertiesProcessorTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2024 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 static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.cloud.bindings.boot.SqlServerReplicatedBindingsPropertiesProcessor.TYPE;
+
+import java.nio.file.Paths;
+import java.util.HashMap;
+
+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;
+
+@DisplayName("SQLServerReplicated BindingsPropertiesProcessor")
+public class SqlServerReplicatedBindingsPropertiesProcessorTest {
+
+ private final FluentMap secret = new FluentMap()
+ .withEntry(Binding.TYPE, TYPE)
+ .withEntry("rw-database", "testrw-database")
+ .withEntry("rw-host", "testrw-host")
+ .withEntry("rw-password", "testrw-password")
+ .withEntry("rw-port", "testrw-port")
+ .withEntry("rw-username", "testrw-username")
+ .withEntry("ro-database", "testro-database")
+ .withEntry("ro-host", "testro-host")
+ .withEntry("ro-password", "testro-password")
+ .withEntry("ro-port", "testro-port")
+ .withEntry("ro-username", "testro-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("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new SqlServerReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "com.microsoft.sqlserver.jdbc.SQLServerDriver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "jdbc:sqlserver://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "com.microsoft.sqlserver.jdbc.SQLServerDriver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "jdbc:sqlserver://testro-host:testro-port/testro-database")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("gives precedence to jdbc-url")
+ void testJdbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-jdbc-url", "testrw-jdbc-url").withEntry("ro-jdbc-url", "testro-jdbc-url"))
+ );
+ new SqlServerReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.datasource.replicated.rw.driver-class-name", "com.microsoft.sqlserver.jdbc.SQLServerDriver")
+ .containsEntry("spring.datasource.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.datasource.replicated.rw.url", "testrw-jdbc-url")
+ .containsEntry("spring.datasource.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.datasource.replicated.ro.driver-class-name", "com.microsoft.sqlserver.jdbc.SQLServerDriver")
+ .containsEntry("spring.datasource.replicated.ro.password", "testro-password")
+ .containsEntry("spring.datasource.replicated.ro.url", "testro-jdbc-url")
+ .containsEntry("spring.datasource.replicated.ro.username", "testro-username")
+ .containsEntry("spring.datasource.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("composes r2dbc url from host port and database")
+ void testR2dbc() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ new SqlServerReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "r2dbc:sqlserver://testrw-host:testrw-port/testrw-database")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "r2dbc:sqlserver://testro-host:testro-port/testro-database")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("gives precedence to r2dbc-url")
+ void testR2dbcURL() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret.withEntry("rw-r2dbc-url", "testrw-r2dbc-url").withEntry("ro-r2dbc-url", "testro-r2dbc-url"))
+ );
+ new SqlServerReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+ assertThat(properties)
+ .containsEntry("spring.r2dbc.replicated.rw.password", "testrw-password")
+ .containsEntry("spring.r2dbc.replicated.rw.url", "testrw-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.rw.username", "testrw-username")
+ .containsEntry("spring.r2dbc.replicated.ro.password", "testro-password")
+ .containsEntry("spring.r2dbc.replicated.ro.url", "testro-r2dbc-url")
+ .containsEntry("spring.r2dbc.replicated.ro.username", "testro-username")
+ .containsEntry("spring.r2dbc.replicated.name", "dbrw");
+ }
+
+ @Test
+ @DisplayName("can be disabled")
+ void disabled() {
+ Bindings bindings = new Bindings(
+ new Binding("dbrw", Paths.get("dbrw-path"), secret)
+ );
+ environment.setProperty("org.springframework.cloud.bindings.boot.sqlserver-replicated.enable", "false");
+
+ new SqlServerReplicatedBindingsPropertiesProcessor().process(environment, bindings, properties);
+
+ assertThat(properties).isEmpty();
+ }
+}