diff --git a/docs/src/main/asciidoc/mongodb.adoc b/docs/src/main/asciidoc/mongodb.adoc index cd3cbfc4b3d14..a08b5a42a1257 100644 --- a/docs/src/main/asciidoc/mongodb.adoc +++ b/docs/src/main/asciidoc/mongodb.adoc @@ -746,6 +746,55 @@ quarkus.mongodb.native.dns.use-vertx-dns-resolver=true quarkus.mongodb.native.dns.lookup-timeout=10s # the default is 5s ---- +== Customize the Mongo client configuration programmatically + +If you need to customize the Mongo client configuration programmatically, you need to implement the `io.quarkus.mongodb.runtime.MongoClientCustomizer` interface and expose it as a CDI _application scoped_ bean: + +[source, java] +---- +@ApplicationScoped +public class MyCustomizer implements MongoClientCustomizer { + + @Override + public MongoClientSettings.Builder customize(MongoClientSettings.Builder builder) { + return builder.applicationName("my-app"); + } +} +---- + +The bean can customize a specific client using the `@MongoClientName` qualifier to indicate the client name. +When there is no qualifier, it customizes the default client. +At most one customizer can be used per client. +If multiple customizers targeting the same client are detected, an exception is thrown at build time. + +This feature can be used to configure client-side field level encryption (CSFLE). +Follows the instructions from https://www.mongodb.com/docs/manual/core/csfle/[the Mongo web site] to configure CSFLE: + +[source, java] +---- +@ApplicationScoped +public class MyCustomizer implements MongoClientCustomizer { + @Override + public MongoClientSettings.Builder customize(MongoClientSettings.Builder builder) { + Map> kmsProviders = getKmsProviders(); + String dek = getDataEncryptionKey(); + Map schema = getSchema(dek); + + Map extraOptions = new HashMap<>(); + extraOptions.put("cryptSharedLibPath", ""); + + return builder.autoEncryptionSettings(AutoEncryptionSettings.builder() + .keyVaultNamespace(KEY_VAULT_NAMESPACE) + .kmsProviders(kmsProviders) + .schemaMap(schemaMap) + .extraOptions(extraOptions) + .build()); + } +} +---- + +IMPORTANT: Client-side field level encryption, and feature relying on https://github.com/mongodb/libmongocrypt[Mongo Crypt] in general, are not supported in native mode. + == Configuration Reference include::{generated-dir}/config/quarkus-mongodb.adoc[opts=optional, leveloffset=+1] diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java index 603df0007a483..744b77a5cfeab 100644 --- a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java +++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java @@ -5,8 +5,10 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; @@ -34,8 +36,11 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem; import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.arc.deployment.ValidationPhaseBuildItem; +import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BuildExtension; import io.quarkus.arc.processor.DotNames; import io.quarkus.arc.processor.InjectionPointInfo; @@ -60,6 +65,7 @@ import io.quarkus.mongodb.MongoClientName; import io.quarkus.mongodb.reactive.ReactiveMongoClient; import io.quarkus.mongodb.runtime.MongoClientBeanUtil; +import io.quarkus.mongodb.runtime.MongoClientCustomizer; import io.quarkus.mongodb.runtime.MongoClientRecorder; import io.quarkus.mongodb.runtime.MongoClientSupport; import io.quarkus.mongodb.runtime.MongoClients; @@ -79,6 +85,8 @@ public class MongoClientProcessor { private static final DotName MONGO_CLIENT = DotName.createSimple(MongoClient.class.getName()); private static final DotName REACTIVE_MONGO_CLIENT = DotName.createSimple(ReactiveMongoClient.class.getName()); + private static final DotName MONGO_CLIENT_CUSTOMIZER = DotName.createSimple(MongoClientCustomizer.class.getName()); + private static final String SERVICE_BINDING_INTERFACE_NAME = "io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter"; @BuildStep @@ -433,4 +441,37 @@ void runtimeInitializedClasses(BuildProducer r runtimeInitializedClasses.produce(new RuntimeInitializedClassBuildItem(ObjectId.class.getName())); runtimeInitializedClasses.produce(new RuntimeInitializedClassBuildItem("com.mongodb.internal.dns.DefaultDnsResolver")); } + + /** + * Ensure we have at most one customizer per Mongo client. + * + * @param beans the beans + * @param validation the producer used to report issues + */ + @BuildStep + void validateMongoConfigCustomizers(BeanDiscoveryFinishedBuildItem beans, + BuildProducer validation) { + HashMap> customizers = new HashMap<>(); + + for (BeanInfo bean : beans.getBeans()) { + if (bean.hasType(MONGO_CLIENT_CUSTOMIZER)) { + var name = bean.getQualifier(MONGO_CLIENT_ANNOTATION); + if (name.isPresent()) { + String clientName = name.get().value().asString(); + customizers.computeIfAbsent(clientName, k -> new ArrayList<>()).add(bean.getBeanClass().toString()); + } else { + customizers.computeIfAbsent(MongoClientBeanUtil.DEFAULT_MONGOCLIENT_NAME, k -> new ArrayList<>()) + .add(bean.getBeanClass().toString()); + } + } + } + + for (Map.Entry> entry : customizers.entrySet()) { + if (entry.getValue().size() > 1) { + validation.produce(new ValidationPhaseBuildItem.ValidationErrorBuildItem( + new IllegalStateException("Multiple Mongo client customizers found for client " + entry.getKey() + ": " + + String.join(", ", entry.getValue())))); + } + } + } } diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/DefaultCustomizerTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/DefaultCustomizerTest.java new file mode 100644 index 0000000000000..8a8550ca49f67 --- /dev/null +++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/DefaultCustomizerTest.java @@ -0,0 +1,43 @@ +package io.quarkus.mongodb.customization; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.internal.MongoClientImpl; + +import io.quarkus.arc.ClientProxy; +import io.quarkus.mongodb.MongoTestBase; +import io.quarkus.mongodb.runtime.MongoClientCustomizer; +import io.quarkus.test.QuarkusUnitTest; + +public class DefaultCustomizerTest extends MongoTestBase { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class, MyCustomizer.class)) + .withConfigurationResource("default-mongoclient.properties"); + + @Inject + MongoClient client; + + @Test + void testCustomizationOnDefaultConnection() { + MongoClientImpl clientImpl = (MongoClientImpl) ClientProxy.unwrap(client); + Assertions.assertThat(clientImpl.getSettings().getApplicationName()).isEqualTo("my-app"); + } + + @ApplicationScoped + public static class MyCustomizer implements MongoClientCustomizer { + + @Override + public MongoClientSettings.Builder customize(MongoClientSettings.Builder builder) { + return builder.applicationName("my-app"); + } + } +} diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/NamedCustomizerTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/NamedCustomizerTest.java new file mode 100644 index 0000000000000..a91682600db5d --- /dev/null +++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/NamedCustomizerTest.java @@ -0,0 +1,60 @@ +package io.quarkus.mongodb.customization; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.internal.MongoClientImpl; + +import io.quarkus.arc.ClientProxy; +import io.quarkus.mongodb.MongoClientName; +import io.quarkus.mongodb.MongoTestBase; +import io.quarkus.mongodb.runtime.MongoClientCustomizer; +import io.quarkus.test.QuarkusUnitTest; + +public class NamedCustomizerTest extends MongoTestBase { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class, MyCustomizer.class)) + .withConfigurationResource("named-mongoclient.properties"); + + @Inject + MongoClient client; + + @Inject + @MongoClientName("second") + MongoClient secondClient; + + @Test + void testCustomizationOnTwoConnections() { + MongoClientImpl clientImpl = (MongoClientImpl) ClientProxy.unwrap(client); + MongoClientImpl secondClientImpl = (MongoClientImpl) ClientProxy.unwrap(secondClient); + Assertions.assertThat(clientImpl.getSettings().getApplicationName()).isEqualTo("my-app"); + Assertions.assertThat(secondClientImpl.getSettings().getApplicationName()).isEqualTo("my-second-app"); + } + + @ApplicationScoped + public static class MyCustomizer implements MongoClientCustomizer { + + @Override + public MongoClientSettings.Builder customize(MongoClientSettings.Builder builder) { + return builder.applicationName("my-app"); + } + } + + @ApplicationScoped + @MongoClientName("second") + public static class MySecondCustomizer implements MongoClientCustomizer { + + @Override + public MongoClientSettings.Builder customize(MongoClientSettings.Builder builder) { + return builder.applicationName("my-second-app"); + } + } +} diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/TooManyDefaultCustomizersTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/TooManyDefaultCustomizersTest.java new file mode 100644 index 0000000000000..aeecdb4cac57d --- /dev/null +++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/TooManyDefaultCustomizersTest.java @@ -0,0 +1,54 @@ +package io.quarkus.mongodb.customization; + +import static org.junit.jupiter.api.Assertions.fail; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.DeploymentException; +import jakarta.inject.Inject; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; + +import io.quarkus.mongodb.MongoTestBase; +import io.quarkus.mongodb.runtime.MongoClientCustomizer; +import io.quarkus.test.QuarkusUnitTest; + +public class TooManyDefaultCustomizersTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class, MyCustomizer.class, MySecondCustomizer.class)) + .withConfigurationResource("default-mongoclient.properties") + .assertException(t -> Assertions.assertThat(t).isInstanceOf(DeploymentException.class) + .hasMessageContaining("Multiple Mongo client customizers found for client : ")); + + @Inject + MongoClient client; + + @Test + void test() { + fail("Should not be run"); + } + + @ApplicationScoped + public static class MyCustomizer implements MongoClientCustomizer { + + @Override + public MongoClientSettings.Builder customize(MongoClientSettings.Builder builder) { + return builder.applicationName("my-app"); + } + } + + @ApplicationScoped + public static class MySecondCustomizer implements MongoClientCustomizer { + + @Override + public MongoClientSettings.Builder customize(MongoClientSettings.Builder builder) { + return builder.applicationName("my-second-app"); + } + } +} diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/TooManyNamedCustomizersTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/TooManyNamedCustomizersTest.java new file mode 100644 index 0000000000000..bf611eedb174c --- /dev/null +++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/customization/TooManyNamedCustomizersTest.java @@ -0,0 +1,58 @@ +package io.quarkus.mongodb.customization; + +import static org.junit.jupiter.api.Assertions.fail; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.DeploymentException; +import jakarta.inject.Inject; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; + +import io.quarkus.mongodb.MongoClientName; +import io.quarkus.mongodb.MongoTestBase; +import io.quarkus.mongodb.runtime.MongoClientCustomizer; +import io.quarkus.test.QuarkusUnitTest; + +public class TooManyNamedCustomizersTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class, MyCustomizer.class, MySecondCustomizer.class)) + .withConfigurationResource("named-mongoclient.properties") + .assertException(t -> Assertions.assertThat(t).isInstanceOf(DeploymentException.class) + .hasMessageContaining("Multiple Mongo client customizers found for client second: ")); + + @Inject + @MongoClientName("second") + MongoClient client; + + @Test + void test() { + fail("Should not be run"); + } + + @ApplicationScoped + @MongoClientName("second") + public static class MyCustomizer implements MongoClientCustomizer { + + @Override + public MongoClientSettings.Builder customize(MongoClientSettings.Builder builder) { + return builder.applicationName("my-app"); + } + } + + @ApplicationScoped + @MongoClientName("second") + public static class MySecondCustomizer implements MongoClientCustomizer { + + @Override + public MongoClientSettings.Builder customize(MongoClientSettings.Builder builder) { + return builder.applicationName("my-second-app"); + } + } +} diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/MongoClientName.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/MongoClientName.java index 306e94275e71c..d5dbc03ca82af 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/MongoClientName.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/MongoClientName.java @@ -7,6 +7,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; +import jakarta.enterprise.util.AnnotationLiteral; import jakarta.inject.Qualifier; /** @@ -38,4 +39,22 @@ * @return the value */ String value() default ""; + + class Literal extends AnnotationLiteral implements MongoClientName { + + public static Literal of(String value) { + return new Literal(value); + } + + private final String value; + + public Literal(String value) { + this.value = value; + } + + @Override + public String value() { + return value; + } + } } diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java index 8324c19f8fd0f..97d4ef805aa92 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java @@ -205,4 +205,5 @@ public class MongoClientConfig { */ @ConfigItem public Optional uuidRepresentation; + } diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientCustomizer.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientCustomizer.java new file mode 100644 index 0000000000000..fd8194deafed4 --- /dev/null +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientCustomizer.java @@ -0,0 +1,30 @@ +package io.quarkus.mongodb.runtime; + +import com.mongodb.MongoClientSettings; + +/** + * Interface implemented by CDI beans that want to customize the Mongo client configuration. + * This is useful for example to configure client-side field encryption. + *

+ * The implementing bean should use the {@link io.quarkus.mongodb.MongoClientName} qualifier to be used for a specific client. + * A bean implementing this interface without the qualifier will be used for the default client only. + */ +public interface MongoClientCustomizer { + + /** + * Customizes the Mongo client configuration. + *

+ * This method is called during the creation of the MongoClient instance. + * The Quarkus configuration had already been processed. + * It gives you the opportunity to extend that configuration or override the processed configuration. + *

+ * Implementation can decide to ignore the passed builder to build their own. + * However, this should be used with caution as it can lead to unexpected results such as not having the right + * Mongo connection string. + * + * @param builder the builder to customize the MongoClient instance + * @return the builder to use to create the MongoClient instance + */ + MongoClientSettings.Builder customize(MongoClientSettings.Builder builder); + +} diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java index 0c948f3c134ca..cc0b938767c99 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java @@ -11,6 +11,7 @@ import static org.bson.codecs.configuration.CodecRegistries.fromProviders; import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -25,6 +26,7 @@ import jakarta.annotation.PreDestroy; import jakarta.enterprise.inject.Any; import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.Bean; import jakarta.inject.Singleton; import org.bson.codecs.configuration.CodecProvider; @@ -33,7 +35,6 @@ import org.bson.codecs.pojo.Conventions; import org.bson.codecs.pojo.PojoCodecProvider; import org.bson.codecs.pojo.PropertyCodecProvider; -import org.jboss.logging.Logger; import com.mongodb.AuthenticationMechanism; import com.mongodb.Block; @@ -60,23 +61,21 @@ import io.quarkus.arc.InstanceHandle; import io.quarkus.credentials.CredentialsProvider; import io.quarkus.credentials.runtime.CredentialsProviderFinder; +import io.quarkus.mongodb.MongoClientName; import io.quarkus.mongodb.health.MongoHealthCheck; import io.quarkus.mongodb.impl.ReactiveMongoClientImpl; import io.quarkus.mongodb.reactive.ReactiveMongoClient; /** * This class is sort of a producer for {@link MongoClient} and {@link ReactiveMongoClient}. - * + *

* It isn't a CDI producer in the literal sense, but it is marked as a bean * and its {@code createMongoClient} and {@code createReactiveMongoClient} methods are called at runtime in order to produce * the actual client objects. - * - * */ @Singleton public class MongoClients { - private static final Logger LOGGER = Logger.getLogger(MongoClients.class.getName()); private static final Pattern COLON_PATTERN = Pattern.compile(":"); private final MongodbConfig mongodbConfig; @@ -87,16 +86,19 @@ public class MongoClients { private final Map mongoclients = new HashMap<>(); private final Map reactiveMongoClients = new HashMap<>(); + private final Instance customizers; public MongoClients(MongodbConfig mongodbConfig, MongoClientSupport mongoClientSupport, Instance codecProviders, Instance propertyCodecProviders, - Instance commandListeners) { + Instance commandListeners, + @Any Instance customizers) { this.mongodbConfig = mongodbConfig; this.mongoClientSupport = mongoClientSupport; this.codecProviders = codecProviders; this.propertyCodecProviders = propertyCodecProviders; this.commandListeners = commandListeners; + this.customizers = customizers; try { //JDK bug workaround @@ -119,7 +121,7 @@ public MongoClients(MongodbConfig mongodbConfig, MongoClientSupport mongoClientS } public MongoClient createMongoClient(String clientName) throws MongoException { - MongoClientSettings mongoConfiguration = createMongoConfiguration(getMatchingMongoClientConfig(clientName)); + MongoClientSettings mongoConfiguration = createMongoConfiguration(clientName, getMatchingMongoClientConfig(clientName)); MongoClient client = com.mongodb.client.MongoClients.create(mongoConfiguration); mongoclients.put(clientName, client); return client; @@ -127,7 +129,7 @@ public MongoClient createMongoClient(String clientName) throws MongoException { public ReactiveMongoClient createReactiveMongoClient(String clientName) throws MongoException { - MongoClientSettings mongoConfiguration = createMongoConfiguration(getMatchingMongoClientConfig(clientName)); + MongoClientSettings mongoConfiguration = createMongoConfiguration(clientName, getMatchingMongoClientConfig(clientName)); com.mongodb.reactivestreams.client.MongoClient client = com.mongodb.reactivestreams.client.MongoClients .create(mongoConfiguration); ReactiveMongoClientImpl reactive = new ReactiveMongoClientImpl(client); @@ -252,7 +254,7 @@ public void apply(ServerSettings.Builder builder) { } } - private MongoClientSettings createMongoConfiguration(MongoClientConfig config) { + private MongoClientSettings createMongoConfiguration(String name, MongoClientConfig config) { if (config == null) { throw new RuntimeException("mongo config is missing for creating mongo client."); } @@ -326,9 +328,42 @@ private MongoClientSettings createMongoConfiguration(MongoClientConfig config) { settings.uuidRepresentation(config.uuidRepresentation.get()); } + settings = customize(name, settings); + return settings.build(); } + private boolean doesNotHaveClientNameQualifier(Bean bean) { + for (Annotation qualifier : bean.getQualifiers()) { + if (qualifier.annotationType().equals(MongoClientName.class)) { + return false; + } + } + return true; + } + + private MongoClientSettings.Builder customize(String name, MongoClientSettings.Builder settings) { + // If the client name is the default one, we use a customizer that does not have the MongoClientName qualifier. + // Otherwise, we use the one that has the qualifier. + // Note that at build time, we check that we have at most one customizer per client, including for the default one. + if (MongoClientBeanUtil.isDefault(name)) { + var maybe = customizers.handlesStream() + .filter(h -> doesNotHaveClientNameQualifier(h.getBean())) + .findFirst(); // We have at most one customizer without the qualifier. + if (maybe.isEmpty()) { + return settings; + } else { + return maybe.get().get().customize(settings); + } + } else { + Instance selected = customizers.select(MongoClientName.Literal.of(name)); + if (selected.isResolvable()) { // We can use resolvable, as we have at most one customizer per client + return selected.get().customize(settings); + } + return settings; + } + } + private void configureCodecRegistry(CodecRegistry defaultCodecRegistry, MongoClientSettings.Builder settings) { List providers = new ArrayList<>(); for (CodecProvider codecProvider : codecProviders) {