Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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
* <a href="https://www.postgresql.org/docs/14/libpq-connect.html">PostgreSQL Doc</a>.
* <p>
* CockroachDB, which shares the same 'postgresql://' protocol as PostgreSQL, has customized options to meet its
* distributed database nature.
* Refer to <a href="https://www.cockroachlabs.com/docs/v21.2/connection-parameters#additional-connection-parameters">Client Connection Parameters</a>.
*/
protected String buildDbOptions(Binding binding) {
String options = binding.getSecret().getOrDefault(getDBOptionSecretField(), "");
String crdbOption = "";
List<String> 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 <a href="https://www.postgresql.org/docs/14/libpq-connect.html">PostgreSQL Doc</a>
*/
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;
}
}
Original file line number Diff line number Diff line change
@@ -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<String, Object> 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<String, Object> properties, Binding binding, String fnc);

/**
* Gets the binding type of this processor.
* @return The binding type.
*/
protected abstract String getType();

}
Original file line number Diff line number Diff line change
@@ -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<ApplicationPreparedEvent> {

/**
* 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<String, Object> 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());
}
}
Loading