Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions jdbc-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies {
compileOnly(project(":jdbc-grpc"))
compileOnly(libs.grpc.stub)
compileOnly(libs.grpc.protobuf)
compileOnly(libs.grpc.netty) // For DirectDataCloudConnection SSL support

implementation(project(":jdbc-util"))
implementation(libs.slf4j.api)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

import com.salesforce.datacloud.jdbc.core.ConnectionProperties;
import com.salesforce.datacloud.jdbc.core.DataCloudConnection;
import com.salesforce.datacloud.jdbc.core.DirectDataCloudConnectionProperties;
import com.salesforce.datacloud.jdbc.core.GrpcChannelProperties;
import com.salesforce.datacloud.jdbc.core.JdbcDriverStubProvider;
import com.salesforce.datacloud.jdbc.exception.DataCloudJDBCException;
import com.salesforce.datacloud.jdbc.util.DirectDataCloudConnection;
import com.salesforce.datacloud.jdbc.util.JdbcURL;
import com.salesforce.datacloud.jdbc.util.PropertyParsingUtils;
import com.salesforce.datacloud.jdbc.util.SqlErrorCodes;
Expand Down Expand Up @@ -84,6 +86,14 @@ public static DataCloudConnection connectUsingProperties(@NonNull String url, Pr
int port = jdbcUrl.getPort();
val properties = info != null ? (Properties) info.clone() : new Properties();
jdbcUrl.addParametersToProperties(properties);

// Check if this is a direct connection request with SSL/TLS support
if (DirectDataCloudConnection.isDirect(properties)) {
log.info("Using DirectDataCloudConnection for URL: {}", url);
DirectDataCloudConnectionProperties directProps = DirectDataCloudConnectionProperties.of(properties);
return DirectDataCloudConnection.of(url, directProps, properties);
}

val connectionProperties = ConnectionProperties.ofDestructive(properties);
val grpcChannelProperties = GrpcChannelProperties.ofDestructive(properties);
String dataspace = takeOptional(properties, "dataspace").orElse("");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import static com.salesforce.datacloud.jdbc.util.PropertyParsingUtils.takeOptional;

import com.salesforce.datacloud.jdbc.exception.DataCloudJDBCException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import lombok.Builder;
import lombok.Getter;
Expand Down Expand Up @@ -36,6 +38,13 @@ public class ConnectionProperties {
private final StatementProperties statementProperties =
StatementProperties.builder().build();

/**
* Additional headers which can be sent by client as pass-through.
* These are parsed from properties with the "headers." prefix.
*/
@Builder.Default
private final Map<String, String> additionalHeaders = new HashMap<>();

public static ConnectionProperties defaultProperties() {
return builder().build();
}
Expand All @@ -55,6 +64,9 @@ public static ConnectionProperties ofDestructive(Properties props) throws DataCl
takeOptional(props, "externalClientContext").ifPresent(builder::externalClientContext);
builder.statementProperties(StatementProperties.ofDestructive(props));

Map<String, String> additionalHeaders = parseAdditionalHeaders(props);
builder.additionalHeaders(additionalHeaders);

return builder.build();
}

Expand All @@ -76,4 +88,24 @@ public Properties toProperties() {

return props;
}

/**
* Parses additional headers from properties with the "headers." prefix.
* Removes the parsed properties from the input Properties object.
*/
private static Map<String, String> parseAdditionalHeaders(Properties props) {
Map<String, String> headers = new HashMap<>();
props.stringPropertyNames().stream()
.filter(key -> key.startsWith("headers."))
.forEach(key -> {
String headerName = key.substring("headers.".length());
String value = props.getProperty(key);
if (!value.isEmpty()) {
headers.put(headerName, value);
}
// Remove the property from the original Properties object
props.remove(key);
});
return headers;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ static Metadata deriveHeadersFromProperties(String dataspace, ConnectionProperti
if (!dataspace.isEmpty()) {
metadata.put(Metadata.Key.of("dataspace", Metadata.ASCII_STRING_MARSHALLER), dataspace);
}

// Add additional headers from ConnectionProperties
connectionProperties.getAdditionalHeaders().forEach((headerName, value) -> {
metadata.put(Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER), value);
});

return metadata;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/**
* This file is part of https://github.com/forcedotcom/datacloud-jdbc which is released under the
* Apache 2.0 license. See https://github.com/forcedotcom/datacloud-jdbc/blob/main/LICENSE.txt
*/
package com.salesforce.datacloud.jdbc.core;

import com.salesforce.datacloud.jdbc.exception.DataCloudJDBCException;
import java.util.Properties;
import lombok.Builder;
import lombok.Getter;

/**
* Connection properties that control the Direct Data cloud JDBC connection behavior.
*/
@Getter
@Builder
public class DirectDataCloudConnectionProperties {
// Property key constants
public static final String direct = "direct";

// property to disable SSL (for testing only, we might change the implementation in future)
public static final String sslDisabled = "ssl_disabled";

// JKS truststore properties - for trust verification
public static final String truststorePath = "truststore_path";
public static final String truststorePassword = "truststore_password";
public static final String truststoreType = "truststore_type";

// PEM certificate properties - for trust verification and client authentication
public static final String clientCertPath = "client_cert_path";
public static final String clientKeyPath = "client_key_path";
public static final String caCertPath = "ca_cert_path";

// Instance fields for parsed property values
@Builder.Default
private final boolean directConnection = false;

@Builder.Default
private final boolean sslDisabledFlag = false;

@Builder.Default
private final String truststorePathValue = "";

@Builder.Default
private final String truststorePasswordValue = "";

@Builder.Default
private final String truststoreTypeValue = "JKS";

@Builder.Default
private final String clientCertPathValue = "";

@Builder.Default
private final String clientKeyPathValue = "";

@Builder.Default
private final String caCertPathValue = "";

/**
* Parses direct connection properties from a Properties object.
*
* @param props The properties to parse
* @return A DirectDataCloudConnectionProperties instance
* @throws DataCloudJDBCException if parsing of property values fails
*/
public static DirectDataCloudConnectionProperties of(Properties props) throws DataCloudJDBCException {
if (props == null) {
return DirectDataCloudConnectionProperties.builder().build();
}

DirectDataCloudConnectionPropertiesBuilder builder = DirectDataCloudConnectionProperties.builder();

// Parse direct connection flag
String directValue = props.getProperty(direct);
if (directValue != null) {
builder.directConnection(Boolean.parseBoolean(directValue));
}

// Parse SSL disabled flag
String sslDisabledValue = props.getProperty(sslDisabled);
if (sslDisabledValue != null) {
builder.sslDisabledFlag(Boolean.parseBoolean(sslDisabledValue));
}

// Parse truststore properties
String truststorePathVal = props.getProperty(truststorePath);
if (truststorePathVal != null) {
builder.truststorePathValue(truststorePathVal);
}

String truststorePasswordVal = props.getProperty(truststorePassword);
if (truststorePasswordVal != null) {
builder.truststorePasswordValue(truststorePasswordVal);
}

String truststoreTypeVal = props.getProperty(truststoreType);
if (truststoreTypeVal != null) {
builder.truststoreTypeValue(truststoreTypeVal);
}

// Parse client certificate properties
String clientCertPathVal = props.getProperty(clientCertPath);
if (clientCertPathVal != null) {
builder.clientCertPathValue(clientCertPathVal);
}

String clientKeyPathVal = props.getProperty(clientKeyPath);
if (clientKeyPathVal != null) {
builder.clientKeyPathValue(clientKeyPathVal);
}

String caCertPathVal = props.getProperty(caCertPath);
if (caCertPathVal != null) {
builder.caCertPathValue(caCertPathVal);
}

return builder.build();
}

/**
* Serializes this instance into a Properties object.
*
* @return A Properties object containing the direct connection properties
*/
public Properties toProperties() {
Properties props = new Properties();

if (directConnection) {
props.setProperty(direct, String.valueOf(directConnection));
}

if (sslDisabledFlag) {
props.setProperty(sslDisabled, String.valueOf(sslDisabledFlag));
}

if (!truststorePathValue.isEmpty()) {
props.setProperty(truststorePath, truststorePathValue);
}

if (!truststorePasswordValue.isEmpty()) {
props.setProperty(truststorePassword, truststorePasswordValue);
}

if (!truststoreTypeValue.equals("JKS")) {
props.setProperty(truststoreType, truststoreTypeValue);
}

if (!clientCertPathValue.isEmpty()) {
props.setProperty(clientCertPath, clientCertPathValue);
}

if (!clientKeyPathValue.isEmpty()) {
props.setProperty(clientKeyPath, clientKeyPathValue);
}

if (!caCertPathValue.isEmpty()) {
props.setProperty(caCertPath, caCertPathValue);
}

return props;
}
}
Loading
Loading