Skip to content

Commit 3c42793

Browse files
authored
Merge pull request #51013 from radcortez/mongo-active-inactive
Support runtime active / inactive Mongo Clients
2 parents 72738b0 + 209c1d7 commit 3c42793

File tree

25 files changed

+608
-369
lines changed

25 files changed

+608
-369
lines changed

docs/src/main/asciidoc/mongodb.adoc

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,71 @@ MongoClient mongoClient1;
303303
ReactiveMongoClient mongoClient2;
304304
----
305305

306+
=== Activate or deactivate Mongo Clients
307+
308+
When a Mongo Client is configured at build time, and its URL is set at runtime, it is active by default. Quarkus
309+
starts the corresponding Mongo Client when the application starts.
310+
311+
To deactivate a Mongo Client at runtime, either:
312+
313+
* Do not set `quarkus.mongodb[.optional name].hosts` or `quarkus.mongodb[.optional name].connection-#string#`.
314+
* Set `quarkus.mongodb[.optional name].active` to `false`.
315+
316+
If a Mongo Client is not active:
317+
318+
* The Mongo Client does not attempt to connect to Mongo during application startup.
319+
* The Mongo Client does not contribute a <<mongo-health-check,health check>>.
320+
* Static CDI injection points involving the Mongo Client, such as `@Inject ReactiveMongoClient mongoClient` or `@Inject MongoClient mongoClient`, cause application startup to fail.
321+
* Dynamic retrieval of the Mongo Client, such as through `CDI.getBeanContainer()`, `Arc.instance()`, or an injected `Instance<ReactiveMongoClient>`, causes an exception to be thrown.
322+
* Other Quarkus extensions that consume the Mongo Client may cause application startup to fail.
323+
324+
This feature is especially useful when the application must dynamically select a Mongo Client from a predefined set at
325+
runtime.
326+
327+
.An example of configuring multiple Mongo Clients for runtime selection:
328+
329+
[source,properties]
330+
----
331+
quarkus.mongodb.one.active=false
332+
quarkus.mongodb.one.connection-string=mongodb://127.0.0.1:27018
333+
quarkus.mongodb.two.active=false
334+
quarkus.mongodb.two.connection-string=mongodb://127.0.0.1:27019
335+
----
336+
337+
[source,java]
338+
----
339+
import io.quarkus.arc.InjectableInstance;
340+
341+
import com.mongodb.client.MongoClient;
342+
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
343+
344+
@ApplicationScoped
345+
public class MyConsumer {
346+
@Inject
347+
@MongoClientName("one")
348+
InjectableInstance<ReactiveMongoClient> one;
349+
@Inject
350+
@MongoClientName("two")
351+
InjectableInstance<MongoClient> two;
352+
public void doSomething() {
353+
ReactiveMongoClient mongoClient = one.getActive();
354+
// ...
355+
}
356+
}
357+
----
358+
359+
Setting `quarkus.mongodb.one.active=true` xref:config-reference.adoc#configuration-sources[at runtime] makes only the
360+
Mongo Client `one` available.
361+
Setting `quarkus.mongodb.two.active=true` at runtime makes only the Mongo Client `two` available.
362+
363+
[IMPORTANT]
364+
====
365+
A Mongo Client (either default or named) must always be discoverable at build-time to be considered for runtime
366+
injection. This can be done by injecting the Mongo Client name with `@MongoClientName`. If the Mongo Client name may be
367+
active or inactive, it needs to use the wrapper `InjectableInstance<>`, or else Quarkus will throw an exception at
368+
startup time if the Mongo Client is inactive. Alternatively, Mongo Clients may also be discovered via configuration.
369+
====
370+
306371
== Running a MongoDB Database
307372
As by default, `MongoClient` is configured to access a local MongoDB database on port 27017 (the default MongoDB port), if you have a local running database on this port, there is nothing more to do before being able to test it!
308373

@@ -605,6 +670,7 @@ The xref:mongodb-panache.adoc[MongoDB with Panache] extension facilitates the us
605670
The xref:liquibase-mongodb.adoc[Liquibase MongoDB] extension facilitates the initialization of a MongoDB database including indices and initial data.
606671
It implements the same schema migration facilities that Liquibase offers for SQL databases.
607672

673+
[[mongo-health-check]]
608674
== Connection Health Check
609675

610676
If you are using the `quarkus-smallrye-health` extension, `quarkus-mongodb-client` will automatically add a readiness health check

extensions/liquibase/liquibase-mongodb/deployment/src/main/java/io/quarkus/liquibase/mongodb/deployment/LiquibaseMongodbProcessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
import io.quarkus.liquibase.mongodb.runtime.LiquibaseMongodbRecorder;
5252
import io.quarkus.maven.dependency.ArtifactCoords;
5353
import io.quarkus.maven.dependency.Dependency;
54-
import io.quarkus.mongodb.runtime.MongoClientBeanUtil;
54+
import io.quarkus.mongodb.runtime.MongoConfig;
5555
import io.quarkus.paths.PathFilter;
5656
import liquibase.change.Change;
5757
import liquibase.change.DatabaseChangeProperty;
@@ -253,7 +253,7 @@ void createBeans(LiquibaseMongodbRecorder recorder,
253253
.unremovable()
254254
.supplier(recorder.liquibaseSupplier(clientName));
255255

256-
if (MongoClientBeanUtil.isDefault(clientName)) {
256+
if (MongoConfig.isDefaultClient(clientName)) {
257257
configurator.addQualifier(Default.class);
258258
} else {
259259
configurator.name(LIQUIBASE_MONGODB_BEAN_NAME_PREFIX + clientName);

extensions/liquibase/liquibase-mongodb/runtime/src/main/java/io/quarkus/liquibase/mongodb/LiquibaseMongodbFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public Liquibase createLiquibase() {
146146

147147
private Database createDatabase(MongoClients clients, String clientName, String databaseName) {
148148
MongoConnection databaseConnection = new MongoConnection();
149-
MongoClient mongoClient = clients.createMongoClient(clientName);
149+
MongoClient mongoClient = clients.unmanagedMongoClient(clientName);
150150
databaseConnection.setMongoClient(mongoClient);
151151
databaseConnection.setMongoDatabase(mongoClient.getDatabase(databaseName));
152152
Database database = new MongoLiquibaseDatabase();

extensions/liquibase/liquibase-mongodb/runtime/src/main/java/io/quarkus/liquibase/mongodb/runtime/LiquibaseMongodbBuildTimeConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import java.util.Map;
44

5-
import io.quarkus.mongodb.runtime.MongoClientBeanUtil;
5+
import io.quarkus.mongodb.runtime.MongoConfig;
66
import io.quarkus.runtime.annotations.ConfigDocMapKey;
77
import io.quarkus.runtime.annotations.ConfigDocSection;
88
import io.quarkus.runtime.annotations.ConfigPhase;
@@ -25,7 +25,7 @@ public interface LiquibaseMongodbBuildTimeConfig {
2525
@ConfigDocMapKey("client-name")
2626
@ConfigDocSection
2727
@WithParentName
28-
@WithUnnamedKey(MongoClientBeanUtil.DEFAULT_MONGOCLIENT_NAME)
28+
@WithUnnamedKey(MongoConfig.DEFAULT_CLIENT_NAME)
2929
@WithDefaults
3030
Map<String, LiquibaseMongodbBuildTimeClientConfig> clientConfigs();
3131
}

extensions/liquibase/liquibase-mongodb/runtime/src/main/java/io/quarkus/liquibase/mongodb/runtime/LiquibaseMongodbConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import java.util.Map;
44

5-
import io.quarkus.mongodb.runtime.MongoClientBeanUtil;
5+
import io.quarkus.mongodb.runtime.MongoConfig;
66
import io.quarkus.runtime.annotations.ConfigDocMapKey;
77
import io.quarkus.runtime.annotations.ConfigDocSection;
88
import io.quarkus.runtime.annotations.ConfigPhase;
@@ -34,7 +34,7 @@ public interface LiquibaseMongodbConfig {
3434
@ConfigDocMapKey("client-name")
3535
@ConfigDocSection
3636
@WithParentName
37-
@WithUnnamedKey(MongoClientBeanUtil.DEFAULT_MONGOCLIENT_NAME)
37+
@WithUnnamedKey(MongoConfig.DEFAULT_CLIENT_NAME)
3838
@WithDefaults
3939
Map<String, LiquibaseMongodbClientConfig> clientConfigs();
4040
}

extensions/liquibase/liquibase-mongodb/runtime/src/main/java/io/quarkus/liquibase/mongodb/runtime/LiquibaseMongodbRecorder.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
import io.quarkus.arc.InjectableInstance;
1212
import io.quarkus.arc.InstanceHandle;
1313
import io.quarkus.liquibase.mongodb.LiquibaseMongodbFactory;
14-
import io.quarkus.mongodb.runtime.MongoClientBeanUtil;
1514
import io.quarkus.mongodb.runtime.MongoClientConfig;
16-
import io.quarkus.mongodb.runtime.MongodbConfig;
15+
import io.quarkus.mongodb.runtime.MongoConfig;
1716
import io.quarkus.runtime.RuntimeValue;
1817
import io.quarkus.runtime.annotations.Recorder;
1918
import liquibase.Liquibase;
@@ -22,12 +21,12 @@
2221
public class LiquibaseMongodbRecorder {
2322
private final LiquibaseMongodbBuildTimeConfig buildTimeConfig;
2423
private final RuntimeValue<LiquibaseMongodbConfig> runtimeConfig;
25-
private final RuntimeValue<MongodbConfig> mongodbRuntimeConfig;
24+
private final RuntimeValue<MongoConfig> mongodbRuntimeConfig;
2625

2726
public LiquibaseMongodbRecorder(
2827
final LiquibaseMongodbBuildTimeConfig buildTimeConfig,
2928
final RuntimeValue<LiquibaseMongodbConfig> runtimeConfig,
30-
final RuntimeValue<MongodbConfig> mongodbRuntimeConfig) {
29+
final RuntimeValue<MongoConfig> mongodbRuntimeConfig) {
3130
this.buildTimeConfig = buildTimeConfig;
3231
this.runtimeConfig = runtimeConfig;
3332
this.mongodbRuntimeConfig = mongodbRuntimeConfig;
@@ -58,18 +57,17 @@ public LiquibaseMongodbFactory get() {
5857
if (liquibaseMongodbClientConfig.mongoClientName().isPresent()) {
5958
// keep compatibility with the legacy configuration which makes possible set the mongo-client-name
6059
String forceMongoClientName = liquibaseMongodbClientConfig.mongoClientName().get();
61-
mongoClientConfig = mongodbRuntimeConfig.getValue().mongoClientConfigs().get(forceMongoClientName);
60+
mongoClientConfig = mongodbRuntimeConfig.getValue().clients().get(forceMongoClientName);
6261
if (mongoClientConfig == null) {
6362
throw new IllegalArgumentException(
6463
"Mongo client named '%s' not found".formatted(forceMongoClientName));
6564
}
6665
clientNameSelected = forceMongoClientName;
67-
} else if (MongoClientBeanUtil.isDefault(clientName)) {
68-
mongoClientConfig = mongodbRuntimeConfig.getValue().defaultMongoClientConfig();
66+
} else if (MongoConfig.isDefaultClient(clientName)) {
67+
mongoClientConfig = mongodbRuntimeConfig.getValue().clients().get(clientName);
6968
clientNameSelected = clientName;
7069
} else {
71-
mongoClientConfig = getRequiredConfig(
72-
mongodbRuntimeConfig.getValue().mongoClientConfigs(),
70+
mongoClientConfig = getRequiredConfig(mongodbRuntimeConfig.getValue().clients(),
7371
"Mongo client named '%s' not found");
7472
clientNameSelected = clientName;
7573
}
@@ -83,7 +81,7 @@ public LiquibaseMongodbFactory get() {
8381
}
8482

8583
private Annotation getLiquibaseMongodbQualifier(String clientName) {
86-
if (MongoClientBeanUtil.isDefault(clientName)) {
84+
if (MongoConfig.isDefaultClient(clientName)) {
8785
return Default.Literal.INSTANCE;
8886
} else {
8987
return LiquibaseMongodbClient.LiquibaseMongodbClientLiteral.of(clientName);

extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/DevServicesMongoProcessor.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.quarkus.mongodb.deployment;
22

33
import static io.quarkus.devservices.common.ContainerLocator.locateContainerWithLabels;
4-
import static io.quarkus.mongodb.runtime.MongoClientBeanUtil.isDefault;
4+
import static io.quarkus.mongodb.runtime.MongoConfig.isDefaultClient;
55

66
import java.io.Closeable;
77
import java.nio.charset.StandardCharsets;
@@ -41,7 +41,7 @@
4141
import io.quarkus.devservices.common.ConfigureUtil;
4242
import io.quarkus.devservices.common.ContainerLocator;
4343
import io.quarkus.devservices.common.Labels;
44-
import io.quarkus.mongodb.runtime.MongodbConfig;
44+
import io.quarkus.mongodb.runtime.MongoConfig;
4545
import io.quarkus.runtime.LaunchMode;
4646
import io.quarkus.runtime.configuration.ConfigUtils;
4747

@@ -164,8 +164,9 @@ private RunningDevService startMongo(DockerStatusBuildItem dockerStatusBuildItem
164164
LaunchMode launchMode, String serviceName) {
165165
if (!capturedProperties.devServicesEnabled) {
166166
// explicitly disabled
167-
log.debug("Not starting devservices for " + (isDefault(connectionName) ? "default datasource" : connectionName)
168-
+ " as it has been disabled in the config");
167+
log.debug(
168+
"Not starting devservices for " + (isDefaultClient(connectionName) ? "default datasource" : connectionName)
169+
+ " as it has been disabled in the config");
169170
return null;
170171
}
171172

@@ -175,14 +176,15 @@ private RunningDevService startMongo(DockerStatusBuildItem dockerStatusBuildItem
175176
&& !ConfigUtils.isPropertyNonEmpty(configPrefix + "hosts");
176177
if (!needToStart) {
177178
// a connection string has been provided
178-
log.debug("Not starting devservices for " + (isDefault(connectionName) ? "default datasource" : connectionName)
179-
+ " as a connection string and/or server addresses have been provided");
179+
log.debug(
180+
"Not starting devservices for " + (isDefaultClient(connectionName) ? "default datasource" : connectionName)
181+
+ " as a connection string and/or server addresses have been provided");
180182
return null;
181183
}
182184

183185
if (!dockerStatusBuildItem.isContainerRuntimeAvailable()) {
184186
log.warn("Please configure datasource URL for "
185-
+ (isDefault(connectionName) ? "default datasource" : connectionName)
187+
+ (isDefaultClient(connectionName) ? "default datasource" : connectionName)
186188
+ " or get a working docker instance");
187189
return null;
188190
}
@@ -240,8 +242,8 @@ private String getEffectiveUrl(String configPrefix, String host, int port, Captu
240242
}
241243

242244
private String getConfigPrefix(String connectionName) {
243-
String configPrefix = "quarkus." + MongodbConfig.CONFIG_NAME + ".";
244-
if (!isDefault(connectionName)) {
245+
String configPrefix = "quarkus." + MongoConfig.CONFIG_NAME + ".";
246+
if (!isDefaultClient(connectionName)) {
245247
configPrefix = configPrefix + connectionName + ".";
246248
}
247249
return configPrefix;

extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
import io.quarkus.mongodb.runtime.MongoClientRecorder;
7272
import io.quarkus.mongodb.runtime.MongoClientSupport;
7373
import io.quarkus.mongodb.runtime.MongoClients;
74+
import io.quarkus.mongodb.runtime.MongoConfig;
7475
import io.quarkus.mongodb.runtime.MongoReactiveContextProvider;
7576
import io.quarkus.mongodb.runtime.MongoServiceBindingConverter;
7677
import io.quarkus.mongodb.runtime.dns.MongoDnsClient;
@@ -280,7 +281,7 @@ void additionalBeans(BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
280281
void connectionNames(
281282
List<MongoClientNameBuildItem> mongoClientNames,
282283
BuildProducer<MongoConnectionNameBuildItem> mongoConnections) {
283-
mongoConnections.produce(new MongoConnectionNameBuildItem(MongoClientBeanUtil.DEFAULT_MONGOCLIENT_NAME));
284+
mongoConnections.produce(new MongoConnectionNameBuildItem(MongoConfig.DEFAULT_CLIENT_NAME));
284285
for (MongoClientNameBuildItem bi : mongoClientNames) {
285286
mongoConnections.produce(new MongoConnectionNameBuildItem(bi.getName()));
286287
}
@@ -368,12 +369,12 @@ void generateClientBeans(MongoClientRecorder recorder,
368369
if (createDefaultBlockingMongoClient) {
369370
syntheticBeanBuildItemBuildProducer.produce(createBlockingSyntheticBean(recorder,
370371
makeUnremovable || mongoClientBuildTimeConfig.forceDefaultClients(),
371-
MongoClientBeanUtil.DEFAULT_MONGOCLIENT_NAME, false));
372+
MongoConfig.DEFAULT_CLIENT_NAME, false));
372373
}
373374
if (createDefaultReactiveMongoClient) {
374375
syntheticBeanBuildItemBuildProducer.produce(createReactiveSyntheticBean(recorder,
375376
makeUnremovable || mongoClientBuildTimeConfig.forceDefaultClients(),
376-
MongoClientBeanUtil.DEFAULT_MONGOCLIENT_NAME, false));
377+
MongoConfig.DEFAULT_CLIENT_NAME, false));
377378
}
378379

379380
for (MongoClientNameBuildItem mongoClientName : mongoClientNames) {
@@ -396,9 +397,9 @@ private SyntheticBeanBuildItem createBlockingSyntheticBean(MongoClientRecorder r
396397
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
397398
.configure(MongoClient.class)
398399
.scope(ApplicationScoped.class)
399-
// pass the runtime config into the recorder to ensure that the DataSource related beans
400-
// are created after runtime configuration has been set up
401400
.supplier(recorder.mongoClientSupplier(clientName))
401+
.checkActive(recorder.checkActive(clientName))
402+
.startup()
402403
.setRuntimeInit();
403404

404405
return applyCommonBeanConfig(makeUnremovable, clientName, addMongoClientQualifier, configurator, false);
@@ -410,9 +411,9 @@ private SyntheticBeanBuildItem createReactiveSyntheticBean(MongoClientRecorder r
410411
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
411412
.configure(ReactiveMongoClient.class)
412413
.scope(ApplicationScoped.class)
413-
// pass the runtime config into the recorder to ensure that the DataSource related beans
414-
// are created after runtime configuration has been set up
415414
.supplier(recorder.reactiveMongoClientSupplier(clientName))
415+
.checkActive(recorder.checkActive(clientName))
416+
.startup()
416417
.setRuntimeInit();
417418

418419
return applyCommonBeanConfig(makeUnremovable, clientName, addMongoClientQualifier, configurator, true);
@@ -424,7 +425,7 @@ private SyntheticBeanBuildItem applyCommonBeanConfig(boolean makeUnremovable, St
424425
configurator.unremovable();
425426
}
426427

427-
if (MongoClientBeanUtil.isDefault(clientName)) {
428+
if (MongoConfig.isDefaultClient(clientName)) {
428429
configurator.addQualifier(Default.class);
429430
} else {
430431
String namedQualifier = MongoClientBeanUtil.namedQualifier(clientName, isReactive);
@@ -505,7 +506,7 @@ void validateMongoConfigCustomizers(BeanDiscoveryFinishedBuildItem beans,
505506
String clientName = name.get().value().asString();
506507
customizers.computeIfAbsent(clientName, k -> new ArrayList<>()).add(bean.getBeanClass().toString());
507508
} else {
508-
customizers.computeIfAbsent(MongoClientBeanUtil.DEFAULT_MONGOCLIENT_NAME, k -> new ArrayList<>())
509+
customizers.computeIfAbsent(MongoConfig.DEFAULT_CLIENT_NAME, k -> new ArrayList<>())
509510
.add(bean.getBeanClass().toString());
510511
}
511512
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.quarkus.mongodb;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import jakarta.inject.Inject;
6+
7+
import org.junit.jupiter.api.Assertions;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.condition.DisabledOnOs;
10+
import org.junit.jupiter.api.condition.OS;
11+
import org.junit.jupiter.api.extension.RegisterExtension;
12+
13+
import com.mongodb.client.MongoClient;
14+
15+
import io.quarkus.arc.InactiveBeanException;
16+
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
17+
import io.quarkus.test.QuarkusUnitTest;
18+
19+
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Flapdoodle doesn't work very well on Windows with replicas")
20+
public class MongoActiveClientsMissingConfigTest {
21+
@RegisterExtension
22+
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
23+
.withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class))
24+
.overrideRuntimeConfigKey("quarkus.mongodb.active.hosts", "")
25+
.overrideRuntimeConfigKey("quarkus.mongodb.active.connection-string", "")
26+
.assertException(e -> assertThat(e)// Can't use isInstanceOf due to weird classloading in tests
27+
.satisfies(t -> assertThat(t.getClass().getName()).isEqualTo(InactiveBeanException.class.getName()))
28+
.hasMessageContainingAll(
29+
"""
30+
Mongo Client 'active' was deactivated automatically because neither the \
31+
hosts nor the connectionString is set. \
32+
To activate the Mongo Client, set the configuration property 'quarkus.mongodb.active.hosts' \
33+
or 'quarkus.mongodb.active.connection-string' \
34+
Refer to https://quarkus.io/guides/mongodb for guidance.
35+
"""));
36+
37+
@Inject
38+
@MongoClientName("active")
39+
MongoClient mongoClient;
40+
@Inject
41+
@MongoClientName("active")
42+
ReactiveMongoClient reactiveMongoClient;
43+
44+
@Test
45+
void missingConfig() {
46+
Assertions.fail();
47+
}
48+
}

0 commit comments

Comments
 (0)