From e694f0dee65187cc0f16df055a33c5872246b502 Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Wed, 25 Oct 2023 14:16:30 -0700
Subject: [PATCH 01/16] Document Supplier/Publisher Option values
---
README.md | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/README.md b/README.md
index 7393988..f100c84 100644
--- a/README.md
+++ b/README.md
@@ -214,6 +214,37 @@ that is specific to Oracle Database and the Oracle JDBC Driver. Extended options
are declared in the
[OracleR2dbcOptions](src/main/java/oracle/r2dbc/OracleR2dbcOptions.java) class.
+#### Support for Supplier and Publisher as Option Values
+The value of _any_ `Option` can be provided by a `Supplier` or `Publisher`. The
+following example configures the `PASSWORD` option with as `Supplier`:
+```java
+ void configurePassword(ConnectionFactoryOptions.Builder optionsBuilder) {
+ Supplier passwordSupplier = () -> getCurrentPassword();
+ optionsBuilder.option(PASSWORD, PASSWORD.cast(passwordSupplier))
+ }
+```
+A similar code example configures `TLS_WALLET_PASSWORD` as a `Publisher`
+```java
+ void configurePassword(ConnectionFactoryOptions.Builder optionsBuilder) {
+ Publisher passwordPublisher = Mono.fromSupplier(() -> getWalletPassword());
+ optionsBuilder.option(TLS_WALLET_PASSWORD, TLS_WALLET_PASSWORD.cast(passwordPublisher))
+ }
+```
+Oracle R2DBC requests an `Option` value from a `Supplier` or `Publisher` each
+`ConnectionFactory.create()` is called to create a new `Connection`. This
+allows connections to be configured with values that change over time, such as a
+password which is periodically rotated.
+
+If an `Option` is configured as a `Supplier`, then Oracle R2DBC requests the
+value of that `Option` by invoking its `Supplier.get()` method. If concurrent
+access to a `ConnectionFactory` is possible, then the `Supplier` must have a
+thread safe `get()` method, as multiple threads may invoke
+`ConnectionFactory.create()` concurrently.
+
+If an `Option` is configured as a `Publisher`, then Oracle R2DBC requests the
+value of that `Option` by subscribing to the `Publisher` and signalling demand.
+The first value emitted to `onNext` will be used as the value of the `Option`.
+
#### Configuring an Oracle Net Descriptor
The `oracle.r2dbc.OracleR2dbcOptions.DESCRIPTOR` option may be used to configure
an Oracle Net Descriptor of the form ```(DESCRIPTION=...)```. If this option is
From c38f9464f80fff4484a2d30073fcaefa75fd7325 Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Wed, 25 Oct 2023 14:45:59 -0700
Subject: [PATCH 02/16] Add Supplier option test
---
.../impl/OracleConnectionFactoryImplTest.java | 87 +++++++++++++------
1 file changed, 61 insertions(+), 26 deletions(-)
diff --git a/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryImplTest.java b/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryImplTest.java
index ec17096..303b770 100644
--- a/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryImplTest.java
+++ b/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryImplTest.java
@@ -35,7 +35,14 @@
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
-
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
+
+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.USER;
import static oracle.r2dbc.test.DatabaseConfig.connectionFactoryOptions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -59,8 +66,8 @@ public void testDiscovery() {
ConnectionFactories
.get(ConnectionFactoryOptions.builder()
.option(ConnectionFactoryOptions.DRIVER, "oracle")
- .option(ConnectionFactoryOptions.HOST, "dbhost")
- .option(ConnectionFactoryOptions.PORT, 1521)
+ .option(HOST, "dbhost")
+ .option(PORT, 1521)
.option(ConnectionFactoryOptions.DATABASE, "service_name")
.build())
.getClass());
@@ -70,8 +77,8 @@ public void testDiscovery() {
ConnectionFactories
.find(ConnectionFactoryOptions.builder()
.option(ConnectionFactoryOptions.DRIVER, "oracle")
- .option(ConnectionFactoryOptions.HOST, "dbhost")
- .option(ConnectionFactoryOptions.PORT, 1521)
+ .option(HOST, "dbhost")
+ .option(PORT, 1521)
.option(ConnectionFactoryOptions.DATABASE, "service_name")
.build())
.getClass());
@@ -107,25 +114,7 @@ public void testDiscovery() {
public void testCreate() {
Publisher extends Connection> connectionPublisher =
new OracleConnectionFactoryImpl(connectionFactoryOptions()).create();
-
- // Expect publisher to emit one connection to each subscriber
- Set connections = new HashSet<>();
- Flux.from(connectionPublisher)
- .doOnNext(connections::add)
- .doOnNext(connection -> Mono.from(connection.close()).subscribe())
- .blockLast(DatabaseConfig.connectTimeout());
- assertEquals(1, connections.size());
- Flux.from(connectionPublisher)
- .doOnNext(connections::add)
- .doOnNext(connection -> Mono.from(connection.close()).subscribe())
- .blockLast(DatabaseConfig.connectTimeout());
- assertEquals(2, connections.size());
- Flux.from(connectionPublisher)
- .doOnNext(connections::add)
- .doOnNext(connection -> Mono.from(connection.close()).subscribe())
- .blockLast(DatabaseConfig.connectTimeout());
- assertEquals(3, connections.size());
-
+ verifyConnectionPublisher(connectionPublisher);
}
/**
@@ -182,12 +171,58 @@ public void testGetMetadata() {
new OracleConnectionFactoryImpl(
ConnectionFactoryOptions.builder()
.option(ConnectionFactoryOptions.DRIVER, "oracle")
- .option(ConnectionFactoryOptions.HOST, "dbhost")
- .option(ConnectionFactoryOptions.PORT, 1521)
+ .option(HOST, "dbhost")
+ .option(PORT, 1521)
.option(ConnectionFactoryOptions.DATABASE, "service_name")
.build())
.getMetadata()
.getName());
}
+ @Test
+ public void testSupplierOption() {
+ Supplier hostSupplier = DatabaseConfig::host;
+ IntSupplier portSupplier = DatabaseConfig::port;
+ Supplier databaseSupplier = DatabaseConfig::serviceName;
+ Supplier userSupplier = DatabaseConfig::user;
+ Supplier passwordSupplier = DatabaseConfig::password;
+
+ ConnectionFactoryOptions connectionFactoryOptions =
+ connectionFactoryOptions()
+ .mutate()
+ .option(HOST, HOST.cast(hostSupplier))
+ .option(PORT, PORT.cast(portSupplier))
+ .option(DATABASE, DATABASE.cast(databaseSupplier))
+ .option(USER, USER.cast(userSupplier))
+ .option(PASSWORD, PASSWORD.cast(passwordSupplier))
+ .build();
+
+ Publisher extends Connection> connectionPublisher =
+ new OracleConnectionFactoryImpl(connectionFactoryOptions).create();
+ verifyConnectionPublisher(connectionPublisher);
+ }
+
+ /** Verifies that a publisher emits connections to multiple subscribers */
+ private static void verifyConnectionPublisher(
+ Publisher extends Connection> connectionPublisher) {
+
+ // Expect publisher to emit one connection to each subscriber
+ Set connections = new HashSet<>();
+ Flux.from(connectionPublisher)
+ .doOnNext(connections::add)
+ .doOnNext(connection -> Mono.from(connection.close()).subscribe())
+ .blockLast(DatabaseConfig.connectTimeout());
+ assertEquals(1, connections.size());
+ Flux.from(connectionPublisher)
+ .doOnNext(connections::add)
+ .doOnNext(connection -> Mono.from(connection.close()).subscribe())
+ .blockLast(DatabaseConfig.connectTimeout());
+ assertEquals(2, connections.size());
+ Flux.from(connectionPublisher)
+ .doOnNext(connections::add)
+ .doOnNext(connection -> Mono.from(connection.close()).subscribe())
+ .blockLast(DatabaseConfig.connectTimeout());
+ assertEquals(3, connections.size());
+ }
+
}
From dfe4808883af4c1e36231e7394c66f586c6b62ce Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Wed, 25 Oct 2023 18:06:57 -0700
Subject: [PATCH 03/16] Specify error and null handling for Supplier/Publisher
options
---
README.md | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index f100c84..2bdcd5b 100644
--- a/README.md
+++ b/README.md
@@ -215,7 +215,7 @@ are declared in the
[OracleR2dbcOptions](src/main/java/oracle/r2dbc/OracleR2dbcOptions.java) class.
#### Support for Supplier and Publisher as Option Values
-The value of _any_ `Option` can be provided by a `Supplier` or `Publisher`. The
+The value of most options can be provided by a `Supplier` or `Publisher`. The
following example configures the `PASSWORD` option with as `Supplier`:
```java
void configurePassword(ConnectionFactoryOptions.Builder optionsBuilder) {
@@ -231,19 +231,30 @@ A similar code example configures `TLS_WALLET_PASSWORD` as a `Publisher`
}
```
Oracle R2DBC requests an `Option` value from a `Supplier` or `Publisher` each
-`ConnectionFactory.create()` is called to create a new `Connection`. This
+time `ConnectionFactory.create()` is called to create a new `Connection`. This
allows connections to be configured with values that change over time, such as a
-password which is periodically rotated.
+password that is periodically rotated.
If an `Option` is configured as a `Supplier`, then Oracle R2DBC requests the
-value of that `Option` by invoking its `Supplier.get()` method. If concurrent
+value of that `Option` by invoking its `get()` method. If concurrent
access to a `ConnectionFactory` is possible, then the `Supplier` must have a
thread safe `get()` method, as multiple threads may invoke
-`ConnectionFactory.create()` concurrently.
+`ConnectionFactory.create()` concurrently. If the `Supplier` returns `null`,
+then no value is configured for the `Option`. If the `Supplier` throws a
+`RuntimeException`, then it is set as the initial cause of an
+`R2dbcException` emitted by `create()` `Publisher`.
If an `Option` is configured as a `Publisher`, then Oracle R2DBC requests the
value of that `Option` by subscribing to the `Publisher` and signalling demand.
The first value emitted to `onNext` will be used as the value of the `Option`.
+If the `Publisher` emits `onComplete` before `onNext`, then no value is
+configured for the `Option`. If the `Publisher` emits a `Throwable` to `onError`
+before `onNext`, it is set as the initial cause of an `R2dbcException` emitted
+by `create()` `Publisher`.
+
+A small subset of `Options` can not be configured with a `Supplier` or
+`Publisher`:
+ - `DRIVER`
#### Configuring an Oracle Net Descriptor
The `oracle.r2dbc.OracleR2dbcOptions.DESCRIPTOR` option may be used to configure
From d1aa4297ec8fa16f5f18fca379e8f5c9c13ce5e4 Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Thu, 26 Oct 2023 18:59:18 -0700
Subject: [PATCH 04/16] Fix typo
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 2bdcd5b..19cdb87 100644
--- a/README.md
+++ b/README.md
@@ -242,15 +242,15 @@ thread safe `get()` method, as multiple threads may invoke
`ConnectionFactory.create()` concurrently. If the `Supplier` returns `null`,
then no value is configured for the `Option`. If the `Supplier` throws a
`RuntimeException`, then it is set as the initial cause of an
-`R2dbcException` emitted by `create()` `Publisher`.
+`R2dbcException` emitted by the `create()` `Publisher`.
If an `Option` is configured as a `Publisher`, then Oracle R2DBC requests the
value of that `Option` by subscribing to the `Publisher` and signalling demand.
The first value emitted to `onNext` will be used as the value of the `Option`.
If the `Publisher` emits `onComplete` before `onNext`, then no value is
configured for the `Option`. If the `Publisher` emits a `Throwable` to `onError`
-before `onNext`, it is set as the initial cause of an `R2dbcException` emitted
-by `create()` `Publisher`.
+before `onNext`, then it is set as the initial cause of an `R2dbcException`
+emitted by the `create()` `Publisher`.
A small subset of `Options` can not be configured with a `Supplier` or
`Publisher`:
From 738c27778fd4ccbaac0c79f4843c7f1104a7f725 Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Thu, 26 Oct 2023 19:01:21 -0700
Subject: [PATCH 05/16] Add common metadata class
---
.../OracleConnectionFactoryMetadataImpl.java | 43 +++++++++++++++++++
1 file changed, 43 insertions(+)
create mode 100644 src/main/java/oracle/r2dbc/impl/OracleConnectionFactoryMetadataImpl.java
diff --git a/src/main/java/oracle/r2dbc/impl/OracleConnectionFactoryMetadataImpl.java b/src/main/java/oracle/r2dbc/impl/OracleConnectionFactoryMetadataImpl.java
new file mode 100644
index 0000000..cf05242
--- /dev/null
+++ b/src/main/java/oracle/r2dbc/impl/OracleConnectionFactoryMetadataImpl.java
@@ -0,0 +1,43 @@
+/*
+ Copyright (c) 2020, 2021, Oracle and/or its affiliates.
+
+ This software is dual-licensed to you under the Universal Permissive License
+ (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License
+ 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
+ either license.
+
+ 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
+
+ https://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 oracle.r2dbc.impl;
+
+import io.r2dbc.spi.ConnectionFactoryMetadata;
+
+/**
+ * Implementation of {@code ConnectionFactoryMetaData} which names
+ * "Oracle Database" as the database product that a
+ * {@link io.r2dbc.spi.ConnectionFactory} connects to.
+ */
+final class OracleConnectionFactoryMetadataImpl
+ implements ConnectionFactoryMetadata {
+
+ static final OracleConnectionFactoryMetadataImpl INSTANCE =
+ new OracleConnectionFactoryMetadataImpl();
+
+ private OracleConnectionFactoryMetadataImpl() {}
+
+ @Override
+ public String getName() {
+ return "Oracle Database";
+ }
+}
From 1e8c3d2ab91e487877b876cad5c2085085dd818e Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Thu, 26 Oct 2023 19:02:28 -0700
Subject: [PATCH 06/16] Use common metadata class
---
.../java/oracle/r2dbc/impl/OracleConnectionFactoryImpl.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/oracle/r2dbc/impl/OracleConnectionFactoryImpl.java b/src/main/java/oracle/r2dbc/impl/OracleConnectionFactoryImpl.java
index 9019153..ae8ecc7 100755
--- a/src/main/java/oracle/r2dbc/impl/OracleConnectionFactoryImpl.java
+++ b/src/main/java/oracle/r2dbc/impl/OracleConnectionFactoryImpl.java
@@ -265,6 +265,6 @@ public Publisher create() {
*/
@Override
public ConnectionFactoryMetadata getMetadata() {
- return () -> "Oracle Database";
+ return OracleConnectionFactoryMetadataImpl.INSTANCE;
}
}
From a6b3345c286ac5d3f04a0b790fcd13c29822fc40 Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Thu, 26 Oct 2023 19:14:10 -0700
Subject: [PATCH 07/16] Adding delegating factory for supplied option values
---
.../impl/SuppliedOptionConnectionFactory.java | 217 ++++++++++++++++++
1 file changed, 217 insertions(+)
create mode 100644 src/main/java/oracle/r2dbc/impl/SuppliedOptionConnectionFactory.java
diff --git a/src/main/java/oracle/r2dbc/impl/SuppliedOptionConnectionFactory.java b/src/main/java/oracle/r2dbc/impl/SuppliedOptionConnectionFactory.java
new file mode 100644
index 0000000..780f4b7
--- /dev/null
+++ b/src/main/java/oracle/r2dbc/impl/SuppliedOptionConnectionFactory.java
@@ -0,0 +1,217 @@
+/*
+ Copyright (c) 2020, 2021, Oracle and/or its affiliates.
+
+ This software is dual-licensed to you under the Universal Permissive License
+ (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License
+ 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
+ either license.
+
+ 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
+
+ https://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 oracle.r2dbc.impl;
+
+import io.r2dbc.spi.Connection;
+import io.r2dbc.spi.ConnectionFactory;
+import io.r2dbc.spi.ConnectionFactoryMetadata;
+import io.r2dbc.spi.ConnectionFactoryOptions;
+import io.r2dbc.spi.Option;
+import oracle.r2dbc.OracleR2dbcOptions;
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * A connection factory having {@link io.r2dbc.spi.ConnectionFactoryOptions}
+ * with values provided by a {@link Supplier} or {@link Publisher}. Supplied
+ * values are requested when {@link #create()} is called. After all requested
+ * values are supplied, this factory delegates to
+ * {@link OracleConnectionFactoryProviderImpl#create(ConnectionFactoryOptions)},
+ * with {@link ConnectionFactoryOptions} composed of the supplied values.
+ */
+final class SuppliedOptionConnectionFactory implements ConnectionFactory {
+
+ /**
+ * The set of all options recognized by Oracle R2DBC. This set includes
+ * the standard options declared by {@link ConnectionFactoryOptions} and
+ * the extended options declared by {@link OracleR2dbcOptions}.
+ *
+ * TODO: This set only includes standard options defined for version 1.0.0 of
+ * the SPI. If a future SPI version introduces new options, those must be
+ * added to this set.
+ */
+ private static final Set
+ * It is not strictly necessary to use this method when configuring an
+ * Option with a value from a Supplier. This method
+ * is offered for code readability and convenience.
+ *
+ * Casts an Option<T> to
+ * Option<Publisher<T>>. For instance, if an
+ * Option<CharSequence> is passed to this method, it
+ * is returned as an
+ * Option<Publisher<CharSequence>>.
+ *
+ * This method can used when configuring an Option with values
+ * from a Publisher:
+ *
+ * It is not strictly necessary to use this method when configuring an
+ * Option with a value from a Publisher. This method
+ * is offered for code readability and convenience.
+ *
+ */
+ public static Option> published(Option option) {
+ @SuppressWarnings("unchecked")
+ Option> publisherOption = (Option>)option;
+ return publisherOption;
+ }
+
}
From e18167f66143625a80a954175ec308db41603f3a Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Thu, 26 Oct 2023 20:17:34 -0700
Subject: [PATCH 11/16] Move supplied option test
---
.../impl/OracleConnectionFactoryImplTest.java | 23 -------------------
1 file changed, 23 deletions(-)
diff --git a/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryImplTest.java b/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryImplTest.java
index 303b770..dec6374 100644
--- a/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryImplTest.java
+++ b/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryImplTest.java
@@ -179,29 +179,6 @@ public void testGetMetadata() {
.getName());
}
- @Test
- public void testSupplierOption() {
- Supplier hostSupplier = DatabaseConfig::host;
- IntSupplier portSupplier = DatabaseConfig::port;
- Supplier databaseSupplier = DatabaseConfig::serviceName;
- Supplier userSupplier = DatabaseConfig::user;
- Supplier passwordSupplier = DatabaseConfig::password;
-
- ConnectionFactoryOptions connectionFactoryOptions =
- connectionFactoryOptions()
- .mutate()
- .option(HOST, HOST.cast(hostSupplier))
- .option(PORT, PORT.cast(portSupplier))
- .option(DATABASE, DATABASE.cast(databaseSupplier))
- .option(USER, USER.cast(userSupplier))
- .option(PASSWORD, PASSWORD.cast(passwordSupplier))
- .build();
-
- Publisher extends Connection> connectionPublisher =
- new OracleConnectionFactoryImpl(connectionFactoryOptions).create();
- verifyConnectionPublisher(connectionPublisher);
- }
-
/** Verifies that a publisher emits connections to multiple subscribers */
private static void verifyConnectionPublisher(
Publisher extends Connection> connectionPublisher) {
From c5bffc25487c40289954ecd5b2bfde8ab11f78b3 Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Thu, 26 Oct 2023 20:18:05 -0700
Subject: [PATCH 12/16] Return error from awaitError
---
src/test/java/oracle/r2dbc/util/Awaits.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/test/java/oracle/r2dbc/util/Awaits.java b/src/test/java/oracle/r2dbc/util/Awaits.java
index a9c241a..30bd782 100644
--- a/src/test/java/oracle/r2dbc/util/Awaits.java
+++ b/src/test/java/oracle/r2dbc/util/Awaits.java
@@ -104,9 +104,9 @@ public static void awaitNone(Publisher> emptyPublisher) {
* @throws Throwable If the publisher emits {@code onError} with a
* {@code Throwable} that is not an instance of {@code errorType}.
*/
- public static void awaitError(
- Class extends Throwable> errorType, Publisher> errorPublisher) {
- assertThrows(
+ public static T awaitError(
+ Class errorType, Publisher> errorPublisher) {
+ return assertThrows(
errorType,
() -> Flux.from(errorPublisher).blockLast(sqlTimeout()),
"Unexpected signal from Publisher of an error");
From f4e9c74b25fff9e5219b67c71ad398365d58b2f1 Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Fri, 27 Oct 2023 12:18:21 -0700
Subject: [PATCH 13/16] Documenting supplied and published methods
---
README.md | 85 +++++++++++++++++++++++++++++++++++++------------------
1 file changed, 57 insertions(+), 28 deletions(-)
diff --git a/README.md b/README.md
index 19cdb87..7ee461a 100644
--- a/README.md
+++ b/README.md
@@ -215,46 +215,75 @@ are declared in the
[OracleR2dbcOptions](src/main/java/oracle/r2dbc/OracleR2dbcOptions.java) class.
#### Support for Supplier and Publisher as Option Values
-The value of most options can be provided by a `Supplier` or `Publisher`. The
-following example configures the `PASSWORD` option with as `Supplier`:
-```java
- void configurePassword(ConnectionFactoryOptions.Builder optionsBuilder) {
- Supplier passwordSupplier = () -> getCurrentPassword();
- optionsBuilder.option(PASSWORD, PASSWORD.cast(passwordSupplier))
- }
-```
-A similar code example configures `TLS_WALLET_PASSWORD` as a `Publisher`
-```java
- void configurePassword(ConnectionFactoryOptions.Builder optionsBuilder) {
- Publisher passwordPublisher = Mono.fromSupplier(() -> getWalletPassword());
- optionsBuilder.option(TLS_WALLET_PASSWORD, TLS_WALLET_PASSWORD.cast(passwordPublisher))
- }
-```
-Oracle R2DBC requests an `Option` value from a `Supplier` or `Publisher` each
-time `ConnectionFactory.create()` is called to create a new `Connection`. This
+Most options can have a value provided by a `Supplier` or `Publisher`.
+
+Oracle R2DBC requests the value of a `Option` from a `Supplier` or `Publisher`
+each time `ConnectionFactory.create()` is called to create a new `Connection`. This
allows connections to be configured with values that change over time, such as a
password that is periodically rotated.
If an `Option` is configured as a `Supplier`, then Oracle R2DBC requests the
-value of that `Option` by invoking its `get()` method. If concurrent
-access to a `ConnectionFactory` is possible, then the `Supplier` must have a
-thread safe `get()` method, as multiple threads may invoke
+value of that `Option` by invoking its `get()` method. If concurrent
+access to a `ConnectionFactory` is possible, then the `Supplier` must have a
+thread safe `get()` method, as multiple threads may invoke
`ConnectionFactory.create()` concurrently. If the `Supplier` returns `null`,
then no value is configured for the `Option`. If the `Supplier` throws a
`RuntimeException`, then it is set as the initial cause of an
`R2dbcException` emitted by the `create()` `Publisher`.
-If an `Option` is configured as a `Publisher`, then Oracle R2DBC requests the
+If an `Option` is configured as a `Publisher`, then Oracle R2DBC requests the
value of that `Option` by subscribing to the `Publisher` and signalling demand.
The first value emitted to `onNext` will be used as the value of the `Option`.
-If the `Publisher` emits `onComplete` before `onNext`, then no value is
-configured for the `Option`. If the `Publisher` emits a `Throwable` to `onError`
-before `onNext`, then it is set as the initial cause of an `R2dbcException`
-emitted by the `create()` `Publisher`.
+If the `Publisher` emits `onComplete` before `onNext`, then no value is
+configured for the `Option`. If the `Publisher` emits `onError` before `onNext`,
+then the `Throwable` is set as the initial cause of an `R2dbcException` emitted
+by the `create()` `Publisher`.
-A small subset of `Options` can not be configured with a `Supplier` or
-`Publisher`:
- - `DRIVER`
+The following example configures the `PASSWORD` option with a `Supplier`:
+```java
+ void configurePassword(ConnectionFactoryOptions.Builder optionsBuilder) {
+
+ // Cast the PASSWORD option
+ Option> suppliedOption = OracleR2dbcOptions.supplied(PASSWORD);
+
+ // Supply a password
+ Supplier supplier = () -> getCurrentPassword();
+
+ // Configure the builder
+ optionsBuilder.option(suppliedOption, supplier);
+ }
+```
+A similar and more concise example configures `TLS_WALLET_PASSWORD` as a `Publisher`
+```java
+ void configurePassword(ConnectionFactoryOptions.Builder optionsBuilder) {
+ optionsBuilder.option(
+ OracleR2dbcOptions.published(TLS_WALLET_PASSWORD),
+ Mono.fromSupplier(() -> getWalletPassword()));
+ }
+```
+These examples use the `supplied(Option)` and `published(Option)` methods
+declared by `oracle.r2dbc.OracleR2dbcOptions`. These methods cast an `Option`
+to `Option>` or `Option>`, respectively. Casting the
+`Option` is required for
+`ConnectionFactoryOptions.Builder.option(Option, T)` to compile and not throw
+a `ClassCastException` at runtime.
+
+It is not strictly necessary to use the `supplied(Option)` or `published(Option)` methods when
+casting the `Option`. These methods are offered only for code readability and
+convenience.
+
+Note that the following code would compile, but fails at runtime with a
+`ClassCastException`:
+```java
+ void configurePassword(ConnectionFactoryOptions.Builder optionsBuilder) {
+ Publisher publisher = Mono.fromSupplier(() -> getPassword()));
+ // Doesn't work. Throws ClassCastException at runtime:
+ optionsBuilder.option(PASSWORD, PASSWORD.cast(publisher));
+ }
+```
+To avoid a `ClassCastException`, the generic type of an `Option` must match the
+actual type of the value passed to
+`ConnectionFactoryOptions.Builder.option(Option, T)`.
#### Configuring an Oracle Net Descriptor
The `oracle.r2dbc.OracleR2dbcOptions.DESCRIPTOR` option may be used to configure
From 26d4901b343e215ddf382e761c77d6db0cbd3bf2 Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Fri, 27 Oct 2023 13:09:03 -0700
Subject: [PATCH 14/16] Specifying unsupported provided options
---
README.md | 30 +++++++++++++++++++-----------
1 file changed, 19 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index 7ee461a..56fc4c9 100644
--- a/README.md
+++ b/README.md
@@ -220,19 +220,19 @@ Most options can have a value provided by a `Supplier` or `Publisher`.
Oracle R2DBC requests the value of a `Option` from a `Supplier` or `Publisher`
each time `ConnectionFactory.create()` is called to create a new `Connection`. This
allows connections to be configured with values that change over time, such as a
-password that is periodically rotated.
+password that gets periodically rotated.
-If an `Option` is configured as a `Supplier`, then Oracle R2DBC requests the
-value of that `Option` by invoking its `get()` method. If concurrent
+If a `Supplier` provides the value of an `Option`, then Oracle R2DBC requests
+the value by invoking `Supplier.get()` method. If the `get()` returns `null`,
+then no value is configured for the `Option`. If `get()` throws a
+`RuntimeException`, then it is set as the initial cause of an
+`R2dbcException` emitted by the `create()` `Publisher`. If concurrent
access to a `ConnectionFactory` is possible, then the `Supplier` must have a
thread safe `get()` method, as multiple threads may invoke
-`ConnectionFactory.create()` concurrently. If the `Supplier` returns `null`,
-then no value is configured for the `Option`. If the `Supplier` throws a
-`RuntimeException`, then it is set as the initial cause of an
-`R2dbcException` emitted by the `create()` `Publisher`.
+`ConnectionFactory.create()` concurrently.
-If an `Option` is configured as a `Publisher`, then Oracle R2DBC requests the
-value of that `Option` by subscribing to the `Publisher` and signalling demand.
+If a `Publisher` provides the value of an `Option`, then Oracle R2DBC requests
+the value by subscribing to the `Publisher` and signalling demand.
The first value emitted to `onNext` will be used as the value of the `Option`.
If the `Publisher` emits `onComplete` before `onNext`, then no value is
configured for the `Option`. If the `Publisher` emits `onError` before `onNext`,
@@ -247,7 +247,7 @@ The following example configures the `PASSWORD` option with a `Supplier`:
Option> suppliedOption = OracleR2dbcOptions.supplied(PASSWORD);
// Supply a password
- Supplier supplier = () -> getCurrentPassword();
+ Supplier supplier = () -> getPassword();
// Configure the builder
optionsBuilder.option(suppliedOption, supplier);
@@ -276,7 +276,7 @@ Note that the following code would compile, but fails at runtime with a
`ClassCastException`:
```java
void configurePassword(ConnectionFactoryOptions.Builder optionsBuilder) {
- Publisher publisher = Mono.fromSupplier(() -> getPassword()));
+ Publisher publisher = Mono.fromSupplier(() -> getPassword());
// Doesn't work. Throws ClassCastException at runtime:
optionsBuilder.option(PASSWORD, PASSWORD.cast(publisher));
}
@@ -285,6 +285,14 @@ To avoid a `ClassCastException`, the generic type of an `Option` must match the
actual type of the value passed to
`ConnectionFactoryOptions.Builder.option(Option, T)`.
+Providing values with a `Supplier` or `Publisher` is not supported for a small
+set of options:
+- `DRIVER`
+- `PROTOCOL`
+
+Providing values for these options would not be interoperable with
+`io.r2dbc.spi.ConnectionFactories` and `r2dbc-pool`.
+
#### Configuring an Oracle Net Descriptor
The `oracle.r2dbc.OracleR2dbcOptions.DESCRIPTOR` option may be used to configure
an Oracle Net Descriptor of the form ```(DESCRIPTION=...)```. If this option is
From 1de4f5b1d3f3961a04def5f68b849c17add8790e Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Fri, 27 Oct 2023 15:37:38 -0700
Subject: [PATCH 15/16] Specify retention of provided option values
---
README.md | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 56fc4c9..3e54b22 100644
--- a/README.md
+++ b/README.md
@@ -223,7 +223,7 @@ allows connections to be configured with values that change over time, such as a
password that gets periodically rotated.
If a `Supplier` provides the value of an `Option`, then Oracle R2DBC requests
-the value by invoking `Supplier.get()` method. If the `get()` returns `null`,
+the value by invoking `Supplier.get()`. If `get()` returns `null`,
then no value is configured for the `Option`. If `get()` throws a
`RuntimeException`, then it is set as the initial cause of an
`R2dbcException` emitted by the `create()` `Publisher`. If concurrent
@@ -293,6 +293,15 @@ set of options:
Providing values for these options would not be interoperable with
`io.r2dbc.spi.ConnectionFactories` and `r2dbc-pool`.
+Normally, Oracle R2DBC will not retain references to `Option` values after
+`ConnectionFactories.create(ConnectionFactoryOptions)` returns. However, if
+the value of at least one `Option` is provided by a `Supplier` or `Publisher`,
+then Oracle R2DBC will retain a reference to all `Option` values until the
+`ConnectionFactory.create()` `Publisher` emits a `Connection` or error. This is
+important to keep in mind when `Option` values may be mutated. In particular,
+a password may only be cleared from memory after the `create()` `Publisher`
+emits a `Connection` or error.
+
#### Configuring an Oracle Net Descriptor
The `oracle.r2dbc.OracleR2dbcOptions.DESCRIPTOR` option may be used to configure
an Oracle Net Descriptor of the form ```(DESCRIPTION=...)```. If this option is
From 40d69a9a36cbf1002fce26abd5bcefa1e401511e Mon Sep 17 00:00:00 2001
From: Michael-A-McMahon
Date: Mon, 30 Oct 2023 11:03:55 -0700
Subject: [PATCH 16/16] Rephrasing supplied options doc
---
README.md | 39 +++++++++++++++++++--------------------
1 file changed, 19 insertions(+), 20 deletions(-)
diff --git a/README.md b/README.md
index 3e54b22..5d606e6 100644
--- a/README.md
+++ b/README.md
@@ -217,27 +217,27 @@ are declared in the
#### Support for Supplier and Publisher as Option Values
Most options can have a value provided by a `Supplier` or `Publisher`.
-Oracle R2DBC requests the value of a `Option` from a `Supplier` or `Publisher`
-each time `ConnectionFactory.create()` is called to create a new `Connection`. This
-allows connections to be configured with values that change over time, such as a
-password that gets periodically rotated.
+Oracle R2DBC requests the value of an `Option` from a `Supplier` or `Publisher`
+each time the `Publisher` returned by `ConnectionFactory.create()` creates a new
+`Connection`. Each `Connection` can then be configured with values that change
+over time, such as a password which is periodically rotated.
If a `Supplier` provides the value of an `Option`, then Oracle R2DBC requests
the value by invoking `Supplier.get()`. If `get()` returns `null`,
then no value is configured for the `Option`. If `get()` throws a
`RuntimeException`, then it is set as the initial cause of an
-`R2dbcException` emitted by the `create()` `Publisher`. If concurrent
-access to a `ConnectionFactory` is possible, then the `Supplier` must have a
-thread safe `get()` method, as multiple threads may invoke
-`ConnectionFactory.create()` concurrently.
+`R2dbcException` emitted by the `Publisher` returned by
+`ConnectionFactory.create()`. The `Supplier` must have a thread safe `get()`
+method, as multiple subscribers may request connections concurrently.
If a `Publisher` provides the value of an `Option`, then Oracle R2DBC requests
the value by subscribing to the `Publisher` and signalling demand.
The first value emitted to `onNext` will be used as the value of the `Option`.
If the `Publisher` emits `onComplete` before `onNext`, then no value is
configured for the `Option`. If the `Publisher` emits `onError` before `onNext`,
-then the `Throwable` is set as the initial cause of an `R2dbcException` emitted
-by the `create()` `Publisher`.
+then the `Throwable` is set as the initial cause of an
+`R2dbcException` emitted by the `Publisher` returned by
+`ConnectionFactory.create()`.
The following example configures the `PASSWORD` option with a `Supplier`:
```java
@@ -253,7 +253,7 @@ The following example configures the `PASSWORD` option with a `Supplier`:
optionsBuilder.option(suppliedOption, supplier);
}
```
-A similar and more concise example configures `TLS_WALLET_PASSWORD` as a `Publisher`
+A more concise example configures `TLS_WALLET_PASSWORD` as a `Publisher`
```java
void configurePassword(ConnectionFactoryOptions.Builder optionsBuilder) {
optionsBuilder.option(
@@ -263,13 +263,12 @@ A similar and more concise example configures `TLS_WALLET_PASSWORD` as a `Publis
```
These examples use the `supplied(Option)` and `published(Option)` methods
declared by `oracle.r2dbc.OracleR2dbcOptions`. These methods cast an `Option`
-to `Option>` or `Option>`, respectively. Casting the
-`Option` is required for
-`ConnectionFactoryOptions.Builder.option(Option, T)` to compile and not throw
-a `ClassCastException` at runtime.
-
-It is not strictly necessary to use the `supplied(Option)` or `published(Option)` methods when
-casting the `Option`. These methods are offered only for code readability and
+to `Option>` and `Option>`, respectively. It is
+necessary to cast the generic type of the `Option` when calling
+`ConnectionFactoryOptions.Builder.option(Option, T)` in order for the call to
+compile and not throw a `ClassCastException` at runtime. It is not strictly
+required that `supplied(Option)` or `published(Option)` be used to cast the
+`Option`. These methods are only meant to offer code readability and
convenience.
Note that the following code would compile, but fails at runtime with a
@@ -285,8 +284,8 @@ To avoid a `ClassCastException`, the generic type of an `Option` must match the
actual type of the value passed to
`ConnectionFactoryOptions.Builder.option(Option, T)`.
-Providing values with a `Supplier` or `Publisher` is not supported for a small
-set of options:
+For a small set of options, providing values with a `Supplier` or `Publisher`
+is not supported:
- `DRIVER`
- `PROTOCOL`