Skip to content

Simplify factory provider #210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 13, 2019
Merged
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
264 changes: 208 additions & 56 deletions src/main/java/io/r2dbc/postgresql/PostgresqlConnectionConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package io.r2dbc.postgresql;


import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.r2dbc.postgresql.client.DefaultHostnameVerifier;
Expand All @@ -26,6 +25,7 @@
import io.r2dbc.postgresql.extension.CodecRegistrar;
import io.r2dbc.postgresql.extension.Extension;
import io.r2dbc.postgresql.util.Assert;
import io.r2dbc.spi.ConnectionFactoryOptions;
import reactor.netty.tcp.SslProvider;
import reactor.util.annotation.Nullable;

Expand All @@ -39,6 +39,26 @@
import java.util.function.Function;
import java.util.function.Supplier;

import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.APPLICATION_NAME;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.AUTODETECT_EXTENSIONS;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.FORCE_BINARY;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.OPTIONS;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SCHEMA;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SOCKET;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SSL_CERT;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SSL_CONTEXT_BUILDER_CUSTOMIZER;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SSL_HOSTNAME_VERIFIER;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SSL_KEY;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SSL_MODE;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SSL_PASSWORD;
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SSL_ROOT_CERT;
import static io.r2dbc.spi.ConnectionFactoryOptions.CONNECT_TIMEOUT;
import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE;
import static io.r2dbc.spi.ConnectionFactoryOptions.HOST;
import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD;
import static io.r2dbc.spi.ConnectionFactoryOptions.PORT;
import static io.r2dbc.spi.ConnectionFactoryOptions.SSL;
import static io.r2dbc.spi.ConnectionFactoryOptions.USER;
import static reactor.netty.tcp.SslProvider.DefaultConfigurationType.TCP;

/**
Expand Down Expand Up @@ -108,33 +128,31 @@ public static Builder builder() {
return new Builder();
}

private static String repeat(int length, String character) {

StringBuilder builder = new StringBuilder();

for (int i = 0; i < length; i++) {
builder.append(character);
}

return builder.toString();
/**
* Returns a new {@link Builder} configured with the given {@link ConnectionFactoryOptions}.
*
* @return a {@link Builder}
*/
public static Builder builder(ConnectionFactoryOptions connectionFactoryOptions) {
return Builder.fromConnectionFactoryOptions(connectionFactoryOptions);
}

@Override
public String toString() {
return "PostgresqlConnectionConfiguration{" +
"applicationName='" + this.applicationName + '\'' +
", autodetectExtensions='" + this.autodetectExtensions + '\'' +
", connectTimeout=" + this.connectTimeout +
", database='" + this.database + '\'' +
", extensions=" + this.extensions +
", forceBinary='" + this.forceBinary + '\'' +
", host='" + this.host + '\'' +
", options='" + this.options + '\'' +
", password='" + repeat(this.password != null ? this.password.length() : 0, "*") + '\'' +
", port=" + this.port +
", schema='" + this.schema + '\'' +
", username='" + this.username + '\'' +
'}';
"applicationName='" + this.applicationName + '\'' +
", autodetectExtensions='" + this.autodetectExtensions + '\'' +
", connectTimeout=" + this.connectTimeout +
", database='" + this.database + '\'' +
", extensions=" + this.extensions +
", forceBinary='" + this.forceBinary + '\'' +
", host='" + this.host + '\'' +
", options='" + this.options + '\'' +
", password='" + obfuscate(this.password != null ? this.password.length() : 0) + '\'' +
", port=" + this.port +
", schema='" + this.schema + '\'' +
", username='" + this.username + '\'' +
'}';
}

String getApplicationName() {
Expand Down Expand Up @@ -226,6 +244,17 @@ SSLConfig getSslConfig() {
return this.sslConfig;
}

private static String obfuscate(int length) {

StringBuilder builder = new StringBuilder();

for (int i = 0; i < length; i++) {
builder.append("*");
}

return builder.toString();
}

/**
* A builder for {@link PostgresqlConnectionConfiguration} instances.
* <p>
Expand Down Expand Up @@ -287,6 +316,60 @@ public static final class Builder {
private Builder() {
}

/**
* Configure the builder with the given {@link ConnectionFactoryOptions}.
*
* @param connectionFactoryOptions {@link ConnectionFactoryOptions}
* @return this {@link Builder}
* @throws IllegalArgumentException if {@code connectionFactoryOptions} is {@code null}
*/
private static Builder fromConnectionFactoryOptions(ConnectionFactoryOptions connectionFactoryOptions) {

Assert.requireNonNull(connectionFactoryOptions, "connectionFactoryOptions must not be null");

Builder builder = new Builder();

builder.connectTimeout(connectionFactoryOptions.getValue(CONNECT_TIMEOUT));
builder.database(connectionFactoryOptions.getValue(DATABASE));
builder.password(connectionFactoryOptions.getValue(PASSWORD));
builder.schema(connectionFactoryOptions.getValue(SCHEMA));
builder.username(connectionFactoryOptions.getRequiredValue(USER));

String applicationName = connectionFactoryOptions.getValue(APPLICATION_NAME);
if (applicationName != null) {
builder.applicationName(applicationName);
}

Object autodetectExtensions = connectionFactoryOptions.getValue(AUTODETECT_EXTENSIONS);
if (autodetectExtensions != null) {
builder.autodetectExtensions(convertToBoolean(autodetectExtensions));
}

Integer port = connectionFactoryOptions.getValue(PORT);
if (port != null) {
builder.port(port);
}

Object forceBinary = connectionFactoryOptions.getValue(FORCE_BINARY);
if (forceBinary != null) {
builder.forceBinary(convertToBoolean(forceBinary));
}

Map<String, String> options = connectionFactoryOptions.getValue(OPTIONS);
if (options != null) {
builder.options(options);
}

if (isUsingTcp(connectionFactoryOptions)) {
builder.host(connectionFactoryOptions.getRequiredValue(HOST));
setupSsl(builder, connectionFactoryOptions);
} else {
builder.socket(connectionFactoryOptions.getRequiredValue(SOCKET));
}

return builder;
}

/**
* Configure the application name. Defaults to {@code postgresql-r2dbc}.
*
Expand Down Expand Up @@ -505,7 +588,7 @@ public Builder sslCert(String sslCert) {
/**
* Configure ssl HostnameVerifier.
*
* @param sslHostnameVerifier {@link javax.net.ssl.HostnameVerifier}
* @param sslHostnameVerifier {@link HostnameVerifier}
* @return this {@link Builder}
*/
public Builder sslHostnameVerifier(HostnameVerifier sslHostnameVerifier) {
Expand Down Expand Up @@ -546,31 +629,6 @@ public Builder sslPassword(@Nullable CharSequence sslPassword) {
return this;
}

@Override
public String toString() {
return "Builder{" +
"applicationName='" + this.applicationName + '\'' +
", autodetectExtensions='" + this.autodetectExtensions + '\'' +
", connectTimeout='" + this.connectTimeout + '\'' +
", database='" + this.database + '\'' +
", extensions='" + this.extensions + '\'' +
", forceBinary='" + this.forceBinary + '\'' +
", host='" + this.host + '\'' +
", parameters='" + this.options + '\'' +
", password='" + repeat(this.password != null ? this.password.length() : 0, "*") + '\'' +
", port=" + this.port +
", schema='" + this.schema + '\'' +
", username='" + this.username + '\'' +
", socket='" + this.socket + '\'' +
", sslContextBuilderCustomizer='" + this.sslContextBuilderCustomizer + '\'' +
", sslMode='" + this.sslMode + '\'' +
", sslRootCert='" + this.sslRootCert + '\'' +
", sslCert='" + this.sslCert + '\'' +
", sslKey='" + this.sslKey + '\'' +
", sslHostnameVerifier='" + this.sslHostnameVerifier + '\'' +
'}';
}

/**
* Configure ssl root cert for server certificate validation.
*
Expand All @@ -594,6 +652,31 @@ public Builder username(String username) {
return this;
}

@Override
public String toString() {
return "Builder{" +
"applicationName='" + this.applicationName + '\'' +
", autodetectExtensions='" + this.autodetectExtensions + '\'' +
", connectTimeout='" + this.connectTimeout + '\'' +
", database='" + this.database + '\'' +
", extensions='" + this.extensions + '\'' +
", forceBinary='" + this.forceBinary + '\'' +
", host='" + this.host + '\'' +
", parameters='" + this.options + '\'' +
", password='" + obfuscate(this.password != null ? this.password.length() : 0) + '\'' +
", port=" + this.port +
", schema='" + this.schema + '\'' +
", username='" + this.username + '\'' +
", socket='" + this.socket + '\'' +
", sslContextBuilderCustomizer='" + this.sslContextBuilderCustomizer + '\'' +
", sslMode='" + this.sslMode + '\'' +
", sslRootCert='" + this.sslRootCert + '\'' +
", sslCert='" + this.sslCert + '\'' +
", sslKey='" + this.sslKey + '\'' +
", sslHostnameVerifier='" + this.sslHostnameVerifier + '\'' +
'}';
}

private SSLConfig createSslConfig() {
if (this.socket != null || this.sslMode == SSLMode.DISABLE) {
return SSLConfig.disabled();
Expand All @@ -618,23 +701,23 @@ private Supplier<SslProvider> createSslProvider() {

// Emulate Libpq behavior
// Determining the default file location
String pathsep = System.getProperty("file.separator");
String defaultdir;
String pathSeparator = System.getProperty("file.separator");
String defaultDir;
if (System.getProperty("os.name").toLowerCase().contains("windows")) { // It is Windows
defaultdir = System.getenv("APPDATA") + pathsep + "postgresql" + pathsep;
defaultDir = System.getenv("APPDATA") + pathSeparator + "postgresql" + pathSeparator;
} else {
defaultdir = System.getProperty("user.home") + pathsep + ".postgresql" + pathsep;
defaultDir = System.getProperty("user.home") + pathSeparator + ".postgresql" + pathSeparator;
}

if (sslCert == null) {
String pathname = defaultdir + "postgresql.crt";
String pathname = defaultDir + "postgresql.crt";
if (new File(pathname).exists()) {
sslCert = pathname;
}
}

if (sslKey == null) {
String pathname = defaultdir + "postgresql.pk8";
String pathname = defaultDir + "postgresql.pk8";
if (new File(pathname).exists()) {
sslKey = pathname;
}
Expand All @@ -645,11 +728,80 @@ private Supplier<SslProvider> createSslProvider() {
sslContextBuilder.keyManager(new File(sslCert), new File(sslKey), sslPassword);
}


return () -> SslProvider.builder()
.sslContext(this.sslContextBuilderCustomizer.apply(sslContextBuilder))
.defaultConfiguration(TCP)
.build();
}

private static void setupSsl(Builder builder, ConnectionFactoryOptions connectionFactoryOptions) {
Boolean ssl = connectionFactoryOptions.getValue(SSL);
if (ssl != null && ssl) {
builder.enableSsl();
}

Object sslMode = connectionFactoryOptions.getValue(SSL_MODE);
if (sslMode != null) {
if (sslMode instanceof String) {
builder.sslMode(SSLMode.fromValue(sslMode.toString()));
} else {
builder.sslMode((SSLMode) sslMode);
}
}

String sslRootCert = connectionFactoryOptions.getValue(SSL_ROOT_CERT);
if (sslRootCert != null) {
builder.sslRootCert(sslRootCert);
}

String sslCert = connectionFactoryOptions.getValue(SSL_CERT);
if (sslCert != null) {
builder.sslCert(sslCert);
}

String sslKey = connectionFactoryOptions.getValue(SSL_KEY);
if (sslKey != null) {
builder.sslKey(sslKey);
}

String sslPassword = connectionFactoryOptions.getValue(SSL_PASSWORD);
if (sslPassword != null) {
builder.sslPassword(sslPassword);
}

if (connectionFactoryOptions.hasOption(SSL_CONTEXT_BUILDER_CUSTOMIZER)) {
builder.sslContextBuilderCustomizer(connectionFactoryOptions.getRequiredValue(SSL_CONTEXT_BUILDER_CUSTOMIZER));
}

setSslHostnameVerifier(builder, connectionFactoryOptions);
}

private static void setSslHostnameVerifier(Builder builder, ConnectionFactoryOptions connectionFactoryOptions) {
Object sslHostnameVerifier = connectionFactoryOptions.getValue(SSL_HOSTNAME_VERIFIER);
if (sslHostnameVerifier != null) {

if (sslHostnameVerifier instanceof String) {

try {
Class<?> verifierClass = Class.forName((String) sslHostnameVerifier);
Object verifier = verifierClass.getConstructor().newInstance();

builder.sslHostnameVerifier((HostnameVerifier) verifier);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Cannot instantiate " + sslHostnameVerifier, e);
}
} else {
builder.sslHostnameVerifier((HostnameVerifier) sslHostnameVerifier);
}
}
}

private static boolean isUsingTcp(ConnectionFactoryOptions connectionFactoryOptions) {
return !connectionFactoryOptions.hasOption(SOCKET);
}

private static boolean convertToBoolean(Object value) {
return value instanceof Boolean ? (boolean) value : Boolean.parseBoolean(value.toString());
}
}
}
Loading