Skip to content

Commit 6a9b68b

Browse files
committed
Fully implement Driver.getPropertyInfo method
Closes #66
1 parent 0d45182 commit 6a9b68b

File tree

10 files changed

+312
-11
lines changed

10 files changed

+312
-11
lines changed

Diff for: CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1212
- Add support for the special CQL command `CONSISTENCY [level]` in `CassandraStatement` (inspired by PR
1313
[#56](https://github.com/ing-bank/cassandra-jdbc-wrapper/pull/56)).
1414
- Add a method `setComplianceMode(String)` in `CassandraDataSource` to specify a specific compliance mode when getting
15-
the connection from a `DataSource` (see issue [#68](https://github.com/ing-bank/cassandra-jdbc-wrapper/issues/68)).
15+
the connection from a `DataSource` (see issue [#68](https://github.com/ing-bank/cassandra-jdbc-wrapper/issues/68),
16+
PR [#69](https://github.com/ing-bank/cassandra-jdbc-wrapper/pull/69)).
1617
### Changed
1718
- Update Java Driver for Apache Cassandra® to version 4.18.1.
1819
- Update Jackson dependencies to version 2.17.1.
1920
- Modify the scale value returned for the type `TIMESTAMP` (see PR
2021
[#58](https://github.com/ing-bank/cassandra-jdbc-wrapper/pull/58)).
2122
- Refactor handling of user-defined types and tuples in `CassandraResultSet.getObject(int | String)` (see PR
2223
[#60](https://github.com/ing-bank/cassandra-jdbc-wrapper/pull/60)).
24+
- Fully implement `CassandraDriver.getPropertyInfo(String, Properties)` (see issue
25+
[#66](https://github.com/ing-bank/cassandra-jdbc-wrapper/issues/66)).
2326
### Fixed
2427
- Fix implementation of the methods `CassandraStatement.execute(String)` and `CassandraPreparedStatement.execute()` to
2528
return `true` even when the result set is empty (see

Diff for: pom.xml

+8
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
<java.version>1.8</java.version>
113113

114114
<!-- Versions for dependencies -->
115+
<approvaltests.version>24.3.0</approvaltests.version>
115116
<checkstyle.version>9.3</checkstyle.version>
116117
<caffeine.version>2.9.3</caffeine.version>
117118
<cassandra-driver-krb5.version>3.0.0</cassandra-driver-krb5.version>
@@ -301,6 +302,13 @@
301302
<version>${slf4j.version}</version>
302303
<scope>test</scope>
303304
</dependency>
305+
<!-- Approval tests -->
306+
<dependency>
307+
<groupId>com.approvaltests</groupId>
308+
<artifactId>approvaltests</artifactId>
309+
<version>${approvaltests.version}</version>
310+
<scope>test</scope>
311+
</dependency>
304312
<!-- Lombok (used only for some tests) -->
305313
<dependency>
306314
<groupId>org.projectlombok</groupId>

Diff for: src/main/java/com/ing/data/cassandra/jdbc/CassandraDriver.java

+51-10
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,56 @@
1818
import com.github.benmanes.caffeine.cache.CacheLoader;
1919
import com.github.benmanes.caffeine.cache.Caffeine;
2020
import com.github.benmanes.caffeine.cache.LoadingCache;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
2123

24+
import javax.annotation.Nonnull;
2225
import java.sql.Connection;
2326
import java.sql.Driver;
2427
import java.sql.DriverManager;
2528
import java.sql.DriverPropertyInfo;
2629
import java.sql.SQLException;
2730
import java.sql.SQLFeatureNotSupportedException;
2831
import java.sql.SQLNonTransientConnectionException;
32+
import java.util.ArrayList;
33+
import java.util.Arrays;
2934
import java.util.Collections;
3035
import java.util.Enumeration;
3136
import java.util.HashMap;
37+
import java.util.List;
3238
import java.util.Map;
3339
import java.util.Properties;
3440

41+
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.buildPropertyInfo;
3542
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.getDriverProperty;
3643
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.safeParseVersion;
3744
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.CONNECTION_CREATION_FAILED;
3845
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED;
46+
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.PROPERTIES_PARSING_FROM_URL_FAILED;
3947
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.PROTOCOL;
48+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_ACTIVE_PROFILE;
49+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CLOUD_SECURE_CONNECT_BUNDLE;
50+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_COMPLIANCE_MODE;
51+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONFIG_FILE;
52+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONNECT_TIMEOUT;
53+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONSISTENCY_LEVEL;
4054
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONTACT_POINTS;
55+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_DEBUG;
56+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_ENABLE_SSL;
57+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_FETCH_SIZE;
58+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_KEEP_ALIVE;
59+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_LOAD_BALANCING_POLICY;
60+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_LOCAL_DATACENTER;
4161
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_PASSWORD;
62+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_RECONNECT_POLICY;
63+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_REQUEST_TIMEOUT;
64+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_RETRY_POLICY;
65+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_SSL_ENGINE_FACTORY;
66+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_SSL_HOSTNAME_VERIFICATION;
67+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_TCP_NO_DELAY;
4268
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_USER;
69+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_USE_KERBEROS;
70+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.parseURL;
4371

4472
/**
4573
* The Cassandra driver implementation.
@@ -56,11 +84,13 @@ public class CassandraDriver implements Driver {
5684
}
5785
}
5886

87+
private static final Logger LOG = LoggerFactory.getLogger(CassandraDriver.class);
88+
5989
// Caching sessions so that multiple CassandraConnections created with the same parameters use the same Session.
6090
private final LoadingCache<Map<String, String>, SessionHolder> sessionsCache = Caffeine.newBuilder()
6191
.build(new CacheLoader<Map<String, String>, SessionHolder>() {
6292
@Override
63-
public SessionHolder load(final Map<String, String> params) throws Exception {
93+
public SessionHolder load(@Nonnull final Map<String, String> params) throws Exception {
6494
return new SessionHolder(params, sessionsCache);
6595
}
6696
});
@@ -121,19 +151,30 @@ public int getMinorVersion() {
121151

122152
@Override
123153
public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties props) {
124-
Properties properties = props;
125-
if (props == null) {
126-
properties = new Properties();
154+
Properties properties;
155+
try {
156+
properties = parseURL(url);
157+
for (Map.Entry<Object, Object> propEntry : props.entrySet()) {
158+
properties.putIfAbsent(propEntry.getKey(), propEntry.getValue());
159+
}
160+
} catch (final SQLException e) {
161+
LOG.warn(PROPERTIES_PARSING_FROM_URL_FAILED, e);
162+
properties = new Properties(props);
127163
}
128-
final DriverPropertyInfo[] info = new DriverPropertyInfo[2];
129164

130-
info[0] = new DriverPropertyInfo(TAG_USER, properties.getProperty(TAG_USER));
131-
info[0].description = "The 'user' property";
165+
// Define the list of availableProperties.
166+
final List<String> availableProperties = Arrays.asList(TAG_USER, TAG_PASSWORD, TAG_LOCAL_DATACENTER, TAG_DEBUG,
167+
TAG_CONSISTENCY_LEVEL, TAG_ACTIVE_PROFILE, TAG_FETCH_SIZE, TAG_LOAD_BALANCING_POLICY, TAG_RETRY_POLICY,
168+
TAG_RECONNECT_POLICY, TAG_ENABLE_SSL, TAG_SSL_ENGINE_FACTORY, TAG_SSL_HOSTNAME_VERIFICATION,
169+
TAG_CLOUD_SECURE_CONNECT_BUNDLE, TAG_USE_KERBEROS, TAG_REQUEST_TIMEOUT, TAG_CONNECT_TIMEOUT,
170+
TAG_TCP_NO_DELAY, TAG_KEEP_ALIVE, TAG_CONFIG_FILE, TAG_COMPLIANCE_MODE);
132171

133-
info[1] = new DriverPropertyInfo(TAG_PASSWORD, properties.getProperty(TAG_PASSWORD));
134-
info[1].description = "The 'password' property";
172+
final List<DriverPropertyInfo> info = new ArrayList<>();
173+
for (String propertyName : availableProperties) {
174+
info.add(buildPropertyInfo(propertyName, properties.get(propertyName)));
175+
}
135176

136-
return info;
177+
return info.toArray(new DriverPropertyInfo[]{});
137178
}
138179

139180
/**

Diff for: src/main/java/com/ing/data/cassandra/jdbc/utils/DriverUtil.java

+23
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424

2525
import java.io.IOException;
2626
import java.io.InputStream;
27+
import java.sql.DriverPropertyInfo;
2728
import java.util.List;
29+
import java.util.Objects;
2830
import java.util.Properties;
2931
import java.util.stream.Collectors;
3032

@@ -163,4 +165,25 @@ public static String buildMetadataList(final List<VersionedMetadata> metadataLis
163165
.collect(Collectors.joining(","));
164166
}
165167

168+
/**
169+
* Builds an instance of {@link DriverPropertyInfo} for the given driver property and value.
170+
*
171+
* @param propertyName The property name.
172+
* @param value The current value of the property.
173+
* @return The driver property info.
174+
*/
175+
public static DriverPropertyInfo buildPropertyInfo(final String propertyName, final Object value) {
176+
final DriverPropertyInfo propertyInfo = new DriverPropertyInfo(propertyName,
177+
Objects.toString(value, StringUtils.EMPTY));
178+
final String driverPropertyDefinition = "driver.properties." + propertyName;
179+
180+
final String propertyChoices = getDriverProperty(driverPropertyDefinition + ".choices");
181+
if (StringUtils.isNotBlank(propertyChoices)) {
182+
propertyInfo.choices = propertyChoices.split(",");
183+
}
184+
propertyInfo.required = Boolean.getBoolean(getDriverProperty(driverPropertyDefinition + ".required"));
185+
propertyInfo.description = getDriverProperty(driverPropertyDefinition);
186+
return propertyInfo;
187+
}
188+
166189
}

Diff for: src/main/java/com/ing/data/cassandra/jdbc/utils/ErrorConstants.java

+5
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,11 @@ public final class ErrorConstants {
376376
public static final String INVALID_PROFILE_NAME =
377377
"No execution profile named [%s], keep the current active profile.";
378378

379+
/**
380+
* Error message used in any SQL exception thrown when trying to extract properties from a JDBC URL.
381+
*/
382+
public static final String PROPERTIES_PARSING_FROM_URL_FAILED = "Failed to extract properties from the given URL.";
383+
379384
private ErrorConstants() {
380385
// Private constructor to hide the public one.
381386
}

Diff for: src/main/resources/jdbc-driver.properties

+30
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,34 @@ driver.version=${project.version}
1818
driver.name=Cassandra JDBC Driver
1919
driver.jdbcVersion=4.0
2020

21+
# The properties supported by the driver.
22+
driver.properties.user=The username used to connect to the database.
23+
driver.properties.password=The password used to connect to the database.
24+
driver.properties.localDatacenter=The local datacenter to use when 'DefaultLoadBalancingPolicy' (used by default) is applied.
25+
driver.properties.debug=Whether the debug mode is enabled.
26+
driver.properties.debug.choices=true,false
27+
driver.properties.consistencyLevel=The consistency level per connection. The consistency level defaults to 'LOCAL_ONE' as defined in the configuration reference if not specified.
28+
driver.properties.activeProfile=The execution profile to use when the connection to Cassandra is created.
29+
driver.properties.fetchSize=The default fetch size for all the queries returning result sets. This value is the number of rows the server will return in each network frame. By default, this value is set to 100.
30+
driver.properties.retry=The custom retry policy to apply. The value must be the full package of the policy's class implementing 'RetryPolicy' interface. By default, the driver will use 'DefaultRetryPolicy'.
31+
driver.properties.loadBalancing=The custom load balancing policy to apply. The value must be the full package of the policy's class implementing 'LoadBalancingPolicy' interface.
32+
driver.properties.reconnection=The custom reconnection policy to apply. By default, the driver will use 'ExponentialReconnectionPolicy'. If you want to use a custom policy, specify the full package of the policy's class. Make sure you always cast the policy's arguments appropriately.
33+
driver.properties.enableSsl=Whether the secured traffic is enabled.
34+
driver.properties.enableSsl.choices=true,false
35+
driver.properties.sslEngineFactory=The SSL engine factory to use. By default, 'DefaultSslEngineFactory' is used. The value must be the fully-qualified name of a class with a no-args constructor implementing 'SslEngineFactory' interface.
36+
driver.properties.hostnameVerification=Whether the validation of the server certificate's common name against the hostname of the server being connected to is enabled. By default, it's enabled if the secured traffic is enabled.
37+
driver.properties.hostnameVerification.choices=true,false
38+
driver.properties.secureConnectBundle=The fully qualified path of the cloud secure connect bundle file used to connect to an AstraDB isntance.
39+
driver.properties.useKerberos=Whether the Kerberos auth provider is enabled. By default, it's disabled.
40+
driver.properties.useKerberos.choices=true,false
41+
driver.properties.requestTimeout=A custom timeout for the queries in milliseconds. By default, the timeout for queries is 2 seconds.
42+
driver.properties.connectTimeout=A custom connection timeout in milliseconds. By default, the connection timeout is 5 seconds.
43+
driver.properties.tcpNoDelay=Whether the Nagle's algorithm is enabled. By default, it's enabled.
44+
driver.properties.tcpNoDelay.choices=true,false
45+
driver.properties.keepAlive=Whether the TCP keep-alive is enabled. By default, it's disabled.
46+
driver.properties.keepAlive.choices=true,false
47+
driver.properties.configFile=The Cassandra client configuration file to use.
48+
driver.properties.complianceMode=The compliance mode used when the connection to the database is established.
49+
driver.properties.complianceMode.choices=Default,Liquibase
50+
2151
database.productName=Cassandra
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.ing.data.cassandra.jdbc;
15+
16+
import org.apache.commons.lang3.ArrayUtils;
17+
import org.approvaltests.Approvals;
18+
import org.approvaltests.strings.Printable;
19+
import org.junit.jupiter.api.Test;
20+
21+
import java.net.InetSocketAddress;
22+
import java.sql.Driver;
23+
import java.sql.DriverManager;
24+
import java.sql.DriverPropertyInfo;
25+
import java.util.Properties;
26+
27+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_CONNECT_TIMEOUT;
28+
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.TAG_DEBUG;
29+
30+
class DriverUnitTest extends UsingCassandraContainerTest {
31+
32+
private static final String KEYSPACE = "test_keyspace";
33+
34+
@Test
35+
void givenJdbcUrlAndProperties_whenGetPropertyInfo_returnExpectedDriverPropertyInfoArray() throws Exception {
36+
final InetSocketAddress contactPoint = cassandraContainer.getContactPoint();
37+
final String jdbcUrl = buildJdbcUrl(contactPoint.getHostName(), contactPoint.getPort(), KEYSPACE,
38+
"localdatacenter=datacenter1");
39+
40+
final Driver sut = DriverManager.getDriver(jdbcUrl);
41+
final Properties props = new Properties();
42+
props.put(TAG_DEBUG, true);
43+
props.put(TAG_CONNECT_TIMEOUT, 10000);
44+
final DriverPropertyInfo[] result = sut.getPropertyInfo(jdbcUrl, props);
45+
final Printable<DriverPropertyInfo>[] printableResult = Printable.create(info ->
46+
info.name + ": " + info.value
47+
+ "\n\t" + info.description
48+
+ "\n\tchoices: " + ArrayUtils.toString(info.choices, "n/a")
49+
+ "\n\trequired: " + info.required
50+
, result);
51+
Approvals.verifyAll("", printableResult);
52+
}
53+
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.ing.data.cassandra.jdbc;
15+
16+
/**
17+
* TestApprovals configuration.
18+
*/
19+
public class PackageSettings {
20+
21+
public static String ApprovalBaseDirectory = "../resources";
22+
public static String UseApprovalSubdirectory = "approvals";
23+
24+
}

Diff for: src/test/java/com/ing/data/cassandra/jdbc/UtilsUnitTest.java

+29
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.junit.jupiter.params.provider.ValueSource;
2626
import org.semver4j.Semver;
2727

28+
import java.sql.DriverPropertyInfo;
2829
import java.sql.SQLException;
2930
import java.sql.SQLNonTransientConnectionException;
3031
import java.sql.SQLSyntaxErrorException;
@@ -40,6 +41,7 @@
4041
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.CASSANDRA_4;
4142
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.CASSANDRA_5;
4243
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.buildMetadataList;
44+
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.buildPropertyInfo;
4345
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.existsInDatabaseVersion;
4446
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.getDriverProperty;
4547
import static com.ing.data.cassandra.jdbc.utils.DriverUtil.safeParseVersion;
@@ -68,8 +70,10 @@
6870
import static com.ing.data.cassandra.jdbc.utils.JdbcUrlUtil.parseURL;
6971
import static org.hamcrest.MatcherAssert.assertThat;
7072
import static org.hamcrest.Matchers.containsInAnyOrder;
73+
import static org.hamcrest.Matchers.hasItems;
7174
import static org.junit.jupiter.api.Assertions.assertEquals;
7275
import static org.junit.jupiter.api.Assertions.assertFalse;
76+
import static org.junit.jupiter.api.Assertions.assertNull;
7377
import static org.junit.jupiter.api.Assertions.assertNotNull;
7478
import static org.junit.jupiter.api.Assertions.assertThrows;
7579
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -330,4 +334,29 @@ void testBuildMetadataList() {
330334
new BasicVersionedMetadata("c", CASSANDRA_5)
331335
), CASSANDRA_4));
332336
}
337+
338+
@Test
339+
void testBuildPropertyInfo() {
340+
DriverPropertyInfo propertyInfo = buildPropertyInfo("prop", null);
341+
assertEquals("prop", propertyInfo.name);
342+
assertEquals(StringUtils.EMPTY, propertyInfo.value);
343+
assertFalse(propertyInfo.required);
344+
assertEquals(StringUtils.EMPTY, propertyInfo.description);
345+
assertNull(propertyInfo.choices);
346+
347+
propertyInfo = buildPropertyInfo("propInt", 1500);
348+
assertEquals("propInt", propertyInfo.name);
349+
assertEquals("1500", propertyInfo.value);
350+
351+
propertyInfo = buildPropertyInfo("propStr", "abc");
352+
assertEquals("propStr", propertyInfo.name);
353+
assertEquals("abc", propertyInfo.value);
354+
355+
propertyInfo = buildPropertyInfo("keepAlive", true);
356+
assertEquals("keepAlive", propertyInfo.name);
357+
assertEquals(Boolean.TRUE.toString(), propertyInfo.value);
358+
assertFalse(propertyInfo.required);
359+
assertEquals("Whether the TCP keep-alive is enabled. By default, it's disabled.", propertyInfo.description);
360+
assertThat(Arrays.asList(propertyInfo.choices), hasItems("true", "false"));
361+
}
333362
}

0 commit comments

Comments
 (0)