Skip to content

Commit 03ed205

Browse files
Support ConnectionFactoryOptions.PROTOCOL
1 parent c35a1dd commit 03ed205

File tree

6 files changed

+121
-88
lines changed

6 files changed

+121
-88
lines changed

src/main/java/oracle/r2dbc/impl/OracleReactiveJdbcAdapter.java

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import static io.r2dbc.spi.ConnectionFactoryOptions.HOST;
6767
import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD;
6868
import static io.r2dbc.spi.ConnectionFactoryOptions.PORT;
69+
import static io.r2dbc.spi.ConnectionFactoryOptions.PROTOCOL;
6970
import static io.r2dbc.spi.ConnectionFactoryOptions.SSL;
7071
import static io.r2dbc.spi.ConnectionFactoryOptions.USER;
7172
import static oracle.r2dbc.impl.OracleR2dbcExceptions.fromJdbc;
@@ -431,9 +432,40 @@ public DataSource createDataSource(ConnectionFactoryOptions options) {
431432
}
432433

433434
/**
435+
* <p>
434436
* Composes an Oracle JDBC URL from {@code ConnectionFactoryOptions}, as
435437
* specified in the javadoc of
436438
* {@link #createDataSource(ConnectionFactoryOptions)}
439+
* </p><p>
440+
* If the {@link ConnectionFactoryOptions#SSL} option is set, then the JDBC
441+
* URL is composed with the tcps protocol, as in:
442+
* {@code jdbc:oracle:thins:@tcps:...}. The {@code SSL} option is interpreted
443+
* as a strict directive to use TLS, and so it takes precedence over any value
444+
* that may otherwise be specified by the {@code PROTOCOL} option.
445+
* </p><p>
446+
* If the {@code SSL} option is not set, then URL is composed with the any
447+
* value set for {@link ConnectionFactoryOptions#PROTOCOL} option. For
448+
* instance, if the {@code PROTOCOL} option is set to "ldap" then the URL
449+
* is composed as: {@code jdbc:oracle:thins:@ldap://...}.
450+
* </p><p>
451+
* For consistency with the Oracle JDBC URL, an Oracle R2DBC URL might include
452+
* multiple space separated LDAP addresses, like this:
453+
* <pre>
454+
* r2dbc:oracle:ldap://example.com:3500/cn=salesdept,cn=OracleContext,dc=com/salesdb%20ldap://example.com:3500/cn=salesdept,cn=OracleContext,dc=com/salesdb
455+
* </pre>
456+
* The %20 encoding of the space character must be used in order for
457+
* {@link ConnectionFactoryOptions#parse(CharSequence)} to recognize the URL
458+
* syntax. When multiple address are specified this way, the {@code DATABASE}
459+
* option will have the value of:
460+
* <pre>
461+
* cn=salesdept,cn=OracleContext,dc=com/salesdb ldap://example.com:3500/cn=salesdept,cn=OracleContext,dc=com/salesdb
462+
* </pre>
463+
* This is unusual, but it is what Oracle JDBC expects to see in the path
464+
* element of a
465+
* <a href="https://docs.oracle.com/en/database/oracle/oracle-database/21/jjdbc/data-sources-and-URLs.html#GUID-F1841136-BE7C-47D4-8AEE-E9E78CA1213D">
466+
* multi-address LDAP URL.
467+
* </a>
468+
* </p>
437469
* @param options R2DBC options. Not null.
438470
* @return An Oracle JDBC URL composed from R2DBC options
439471
* @throws IllegalArgumentException If the {@code oracleNetDescriptor}
@@ -448,15 +480,18 @@ private static String composeJdbcUrl(ConnectionFactoryOptions options) {
448480
return "jdbc:oracle:thin:@" + descriptor.toString();
449481
}
450482
else {
483+
Object protocol =
484+
Boolean.TRUE.equals(parseOptionValue(
485+
SSL, options, Boolean.class, Boolean::valueOf))
486+
? "tcps"
487+
: options.getValue(PROTOCOL);
451488
Object host = options.getRequiredValue(HOST);
452489
Integer port = parseOptionValue(
453490
PORT, options, Integer.class, Integer::valueOf);
454491
Object serviceName = options.getValue(DATABASE);
455-
Boolean isTcps = parseOptionValue(
456-
SSL, options, Boolean.class, Boolean::valueOf);
457492

458493
return String.format("jdbc:oracle:thin:@%s%s%s%s",
459-
Boolean.TRUE.equals(isTcps) ? "tcps:" : "",
494+
protocol == null ? "" : protocol + "://",
460495
host,
461496
port != null ? (":" + port) : "",
462497
serviceName != null ? ("/" + serviceName) : "");

src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryImplTest.java

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.HashSet;
3737
import java.util.Set;
3838

39+
import static oracle.r2dbc.test.DatabaseConfig.connectionFactoryOptions;
3940
import static org.junit.jupiter.api.Assertions.assertEquals;
4041
import static org.junit.jupiter.api.Assertions.assertTrue;
4142
import static org.junit.jupiter.api.Assertions.fail;
@@ -105,16 +106,7 @@ public void testDiscovery() {
105106
@Test
106107
public void testCreate() {
107108
Publisher<? extends Connection> connectionPublisher =
108-
new OracleConnectionFactoryImpl(
109-
ConnectionFactoryOptions.builder()
110-
.option(ConnectionFactoryOptions.DRIVER, "oracle")
111-
.option(ConnectionFactoryOptions.HOST, DatabaseConfig.host())
112-
.option(ConnectionFactoryOptions.PORT, DatabaseConfig.port())
113-
.option(ConnectionFactoryOptions.DATABASE, DatabaseConfig.serviceName())
114-
.option(ConnectionFactoryOptions.USER, DatabaseConfig.user())
115-
.option(ConnectionFactoryOptions.PASSWORD, DatabaseConfig.password())
116-
.build())
117-
.create();
109+
new OracleConnectionFactoryImpl(connectionFactoryOptions()).create();
118110

119111
// Expect publisher to emit one connection to each subscriber
120112
Set<Connection> connections = new HashSet<>();
@@ -145,16 +137,11 @@ public void testCreate() {
145137
public void testCreateFailure() {
146138
// Connect with the wrong username
147139
Publisher<? extends Connection> connectionPublisher =
148-
new OracleConnectionFactoryImpl(
149-
ConnectionFactoryOptions.builder()
150-
.option(ConnectionFactoryOptions.DRIVER, "oracle")
151-
.option(ConnectionFactoryOptions.HOST, DatabaseConfig.host())
152-
.option(ConnectionFactoryOptions.PORT, DatabaseConfig.port())
153-
.option(ConnectionFactoryOptions.DATABASE, DatabaseConfig.serviceName())
154-
.option(ConnectionFactoryOptions.USER,
155-
"Wrong" + DatabaseConfig.user())
156-
.option(ConnectionFactoryOptions.PASSWORD, DatabaseConfig.password())
157-
.build())
140+
new OracleConnectionFactoryImpl(connectionFactoryOptions()
141+
.mutate()
142+
.option(ConnectionFactoryOptions.USER,
143+
"Wrong" + DatabaseConfig.user())
144+
.build())
158145
.create();
159146

160147
// Expect publisher to signal onError with an R2DBCException

src/test/java/oracle/r2dbc/impl/OracleReactiveJdbcAdapterTest.java

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import java.nio.file.StandardOpenOption;
4444
import java.sql.SQLException;
4545
import java.time.Duration;
46+
import java.util.Objects;
47+
import java.util.Optional;
4648
import java.util.Properties;
4749
import java.util.Set;
4850
import java.util.concurrent.CompletableFuture;
@@ -66,9 +68,11 @@
6668
import static io.r2dbc.spi.ConnectionFactoryOptions.USER;
6769
import static java.lang.String.format;
6870
import static oracle.r2dbc.test.DatabaseConfig.connectTimeout;
71+
import static oracle.r2dbc.test.DatabaseConfig.connectionFactoryOptions;
6972
import static oracle.r2dbc.test.DatabaseConfig.host;
7073
import static oracle.r2dbc.test.DatabaseConfig.password;
7174
import static oracle.r2dbc.test.DatabaseConfig.port;
75+
import static oracle.r2dbc.test.DatabaseConfig.protocol;
7276
import static oracle.r2dbc.test.DatabaseConfig.serviceName;
7377
import static oracle.r2dbc.test.DatabaseConfig.sharedConnection;
7478
import static oracle.r2dbc.test.DatabaseConfig.sqlTimeout;
@@ -213,9 +217,11 @@ public void testTnsAdmin() throws IOException {
213217

214218
// Create an Oracle Net Descriptor
215219
String descriptor = format(
216-
"(DESCRIPTION=(ADDRESS=(HOST=%s)(PORT=%d)(PROTOCOL=tcp))" +
220+
"(DESCRIPTION=(ADDRESS=(HOST=%s)(PORT=%d)(PROTOCOL=%s))" +
217221
"(CONNECT_DATA=(SERVICE_NAME=%s)))",
218-
host(), port(), serviceName());
222+
host(), port(),
223+
Objects.requireNonNullElse(protocol(), "tcp"),
224+
serviceName());
219225

220226
// Create a tnsnames.ora file with an alias for the descriptor
221227
Files.writeString(Path.of("tnsnames.ora"),
@@ -365,14 +371,8 @@ public void testConnectTimeout()
365371
@Test
366372
public void testStatementTimeout() {
367373
Connection connection0 =
368-
Mono.from(ConnectionFactories.get(ConnectionFactoryOptions
369-
.builder()
370-
.option(DRIVER, "oracle")
371-
.option(HOST, host())
372-
.option(PORT, port())
373-
.option(DATABASE, serviceName())
374-
.option(USER, user())
375-
.option(PASSWORD, password())
374+
Mono.from(ConnectionFactories.get(connectionFactoryOptions()
375+
.mutate()
376376
.option(STATEMENT_TIMEOUT, Duration.ofSeconds(2))
377377
// Disable OOB to support testing with an 18.x database
378378
.option(Option.valueOf(
@@ -442,14 +442,9 @@ public void testExecutorOption() {
442442

443443
// Create a connection that is configured to use the custom executor
444444
Connection connection = awaitOne(ConnectionFactories.get(
445-
ConnectionFactoryOptions.builder()
445+
connectionFactoryOptions()
446+
.mutate()
446447
.option(OracleR2dbcOptions.EXECUTOR, testExecutor)
447-
.option(DRIVER, "oracle")
448-
.option(HOST, host())
449-
.option(PORT, port())
450-
.option(DATABASE, serviceName())
451-
.option(USER, user())
452-
.option(PASSWORD, password())
453448
.build())
454449
.create());
455450

@@ -490,12 +485,15 @@ public void testVSessionOptions() {
490485
// Verify configuration with URL parameters
491486
Connection connection = awaitOne(ConnectionFactories.get(
492487
ConnectionFactoryOptions.parse(
493-
format("r2dbc:oracle://%s:%d/%s" +
488+
format("r2dbc:oracle:%s//%s:%d/%s" +
494489
"?v$session.osuser=%s" +
495490
"&v$session.terminal=%s" +
496491
"&v$session.process=%s" +
497492
"&v$session.program=%s" +
498493
"&v$session.machine=%s",
494+
Optional.ofNullable(protocol())
495+
.map(protocol -> protocol + ":")
496+
.orElse(""),
499497
host(), port(), serviceName(),
500498
osuser, terminal, process, program, machine))
501499
.mutate()

src/test/java/oracle/r2dbc/impl/OracleStatementImplTest.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import static java.lang.String.format;
6262
import static java.util.Arrays.asList;
6363
import static oracle.r2dbc.test.DatabaseConfig.connectTimeout;
64+
import static oracle.r2dbc.test.DatabaseConfig.connectionFactoryOptions;
6465
import static oracle.r2dbc.test.DatabaseConfig.host;
6566
import static oracle.r2dbc.test.DatabaseConfig.newConnection;
6667
import static oracle.r2dbc.test.DatabaseConfig.password;
@@ -2280,17 +2281,10 @@ public Object getValue() {
22802281
* @return Connection that uses the {@code executor}
22812282
*/
22822283
private static Publisher<? extends Connection> connect(Executor executor) {
2283-
return ConnectionFactories.get(
2284-
ConnectionFactoryOptions.parse(format(
2285-
"r2dbc:oracle://%s:%d/%s", host(), port(), serviceName()))
2286-
.mutate()
2287-
.option(
2288-
ConnectionFactoryOptions.USER, user())
2289-
.option(
2290-
ConnectionFactoryOptions.PASSWORD, password())
2291-
.option(
2292-
OracleR2dbcOptions.EXECUTOR, executor)
2293-
.build())
2284+
return ConnectionFactories.get(connectionFactoryOptions()
2285+
.mutate()
2286+
.option(OracleR2dbcOptions.EXECUTOR, executor)
2287+
.build())
22942288
.create();
22952289
}
22962290

src/test/java/oracle/r2dbc/test/DatabaseConfig.java

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ public final class DatabaseConfig {
4949

5050
private DatabaseConfig() {}
5151

52+
/**
53+
* Returns the protocol used to connect with a database, specified as
54+
* {@code PROTOCOL} in the "config.properties" file.
55+
* @return Connection protocol for the test database. May be {@code null} if
56+
* no protocol is configured.
57+
*/
58+
public static String protocol() {
59+
return PROTOCOL;
60+
}
61+
5262
/**
5363
* Returns the hostname of the server where a test database listens for
5464
* connections, specified as {@code HOST} in the "config.properties" file.
@@ -203,6 +213,40 @@ public static void showErrors(Connection connection) {
203213
.forEach(System.err::println);
204214
}
205215

216+
/**
217+
* Returns the options parsed from the "config.properties" resource.
218+
*/
219+
public static ConnectionFactoryOptions connectionFactoryOptions() {
220+
221+
ConnectionFactoryOptions.Builder optionsBuilder =
222+
ConnectionFactoryOptions.builder()
223+
.option(ConnectionFactoryOptions.DRIVER, "oracle")
224+
.option(ConnectionFactoryOptions.HOST, HOST)
225+
.option(ConnectionFactoryOptions.PORT, PORT)
226+
.option(ConnectionFactoryOptions.DATABASE, SERVICE_NAME)
227+
.option(ConnectionFactoryOptions.USER, USER)
228+
.option(ConnectionFactoryOptions.PASSWORD, PASSWORD)
229+
// Disable statement caching in order to verify cursor closing;
230+
// Cached statements don't close their cursors
231+
.option(Option.valueOf(
232+
OracleConnection.CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE),
233+
0)
234+
// Disable out-of-band breaks to support testing with the 18.x
235+
// database. The 19.x database will automatically detect when it's
236+
// running on a system where OOB is not supported, but the 18.x
237+
// database does not do this and so statement timeout tests will
238+
// hang if the database system does not support OOB
239+
.option(Option.valueOf(
240+
OracleConnection.CONNECTION_PROPERTY_THIN_NET_DISABLE_OUT_OF_BAND_BREAK),
241+
"true");
242+
243+
if (PROTOCOL != null)
244+
optionsBuilder.option(ConnectionFactoryOptions.PROTOCOL, PROTOCOL);
245+
246+
return optionsBuilder.build();
247+
}
248+
249+
private static final String PROTOCOL;
206250
private static final String HOST;
207251
private static final int PORT;
208252
private static final String SERVICE_NAME;
@@ -237,30 +281,9 @@ public static void showErrors(Connection connection) {
237281
Long.parseLong(prop.getProperty("CONNECT_TIMEOUT")));
238282
SQL_TIMEOUT = Duration.ofSeconds(
239283
Long.parseLong(prop.getProperty("SQL_TIMEOUT")));
284+
PROTOCOL = prop.getProperty("PROTOCOL");
240285

241-
CONNECTION_FACTORY = ConnectionFactories.get(
242-
ConnectionFactoryOptions.builder()
243-
.option(ConnectionFactoryOptions.DRIVER, "oracle")
244-
.option(ConnectionFactoryOptions.HOST, HOST)
245-
.option(ConnectionFactoryOptions.PORT, PORT)
246-
.option(ConnectionFactoryOptions.DATABASE, SERVICE_NAME)
247-
.option(ConnectionFactoryOptions.USER, USER)
248-
.option(ConnectionFactoryOptions.PASSWORD, PASSWORD)
249-
// Disable statement caching in order to verify cursor closing;
250-
// Cached statements don't close their cursors
251-
.option(Option.valueOf(
252-
OracleConnection.CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE),
253-
0)
254-
// Disable out-of-band breaks to support testing with the 18.x
255-
// database. The 19.x database will automatically detect when it's
256-
// running on a system where OOB is not supported, but the 18.x
257-
// database does not do this and so statement timeout tests will
258-
// hang if the database system does not support OOB
259-
.option(Option.valueOf(
260-
OracleConnection.CONNECTION_PROPERTY_THIN_NET_DISABLE_OUT_OF_BAND_BREAK),
261-
"true")
262-
.build());
263-
286+
CONNECTION_FACTORY = ConnectionFactories.get(connectionFactoryOptions());
264287
SHARED_CONNECTION_FACTORY = new SharedConnectionFactory(
265288
CONNECTION_FACTORY.create(),
266289
CONNECTION_FACTORY.getMetadata());

src/test/java/oracle/r2dbc/test/OracleTestKit.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.math.BigDecimal;
4646
import java.sql.SQLException;
4747
import java.util.Arrays;
48+
import java.util.Optional;
4849
import java.util.function.Function;
4950
import java.util.stream.IntStream;
5051

@@ -54,9 +55,11 @@
5455
import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD;
5556
import static io.r2dbc.spi.ConnectionFactoryOptions.PORT;
5657
import static io.r2dbc.spi.ConnectionFactoryOptions.USER;
58+
import static oracle.r2dbc.test.DatabaseConfig.connectionFactoryOptions;
5759
import static oracle.r2dbc.test.DatabaseConfig.host;
5860
import static oracle.r2dbc.test.DatabaseConfig.password;
5961
import static oracle.r2dbc.test.DatabaseConfig.port;
62+
import static oracle.r2dbc.test.DatabaseConfig.protocol;
6063
import static oracle.r2dbc.test.DatabaseConfig.serviceName;
6164
import static oracle.r2dbc.test.DatabaseConfig.user;
6265

@@ -91,7 +94,10 @@ public class OracleTestKit implements TestKit<Integer> {
9194
{
9295
try {
9396
OracleDataSource dataSource = new oracle.jdbc.pool.OracleDataSource();
94-
dataSource.setURL(String.format("jdbc:oracle:thin:@%s:%d/%s",
97+
dataSource.setURL(String.format("jdbc:oracle:thin:@%s%s:%d/%s",
98+
Optional.ofNullable(protocol())
99+
.map(protocol -> protocol + ":")
100+
.orElse(""),
95101
host(), port(), serviceName()));
96102
dataSource.setUser(user());
97103
dataSource.setPassword(password());
@@ -102,18 +108,8 @@ public class OracleTestKit implements TestKit<Integer> {
102108
}
103109
}
104110

105-
private final ConnectionFactory connectionFactory;
106-
{
107-
connectionFactory = ConnectionFactories.get(
108-
ConnectionFactoryOptions.builder()
109-
.option(DRIVER, "oracle")
110-
.option(DATABASE, serviceName())
111-
.option(HOST, host())
112-
.option(PORT, port())
113-
.option(PASSWORD, password())
114-
.option(USER, user())
115-
.build());
116-
}
111+
private final ConnectionFactory connectionFactory =
112+
ConnectionFactories.get(connectionFactoryOptions());
117113

118114
public JdbcOperations getJdbcOperations() {
119115
return jdbcOperations;

0 commit comments

Comments
 (0)