Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.grails.datastore.mapping.mongo.connections

import com.mongodb.MongoClientSettings

/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link MongoClientSettings.Builder} used to construct the underlying
* {@link com.mongodb.client.MongoClient} before it is created.
*
* <p>Any beans of this type found in the application context are applied, in order,
* to the builder for the default connection source. This is the supported extension
* point for settings that cannot be expressed through {@code grails.mongodb.*}
* configuration — for example registering a driver
* {@link com.mongodb.event.CommandListener} for metrics/tracing, tuning the connection
* pool, or configuring read/write concerns programmatically.</p>
*
* <pre class="code">
* &#64;Bean
* MongoClientSettingsBuilderCustomizer mongoMetrics(MeterRegistry registry) {
* return { builder -> builder.addCommandListener(new MongoMetricsCommandListener(registry)) }
* }
* </pre>
*
* <p>Mirrors the semantics of Spring Boot's
* {@code MongoClientSettingsBuilderCustomizer}.</p>
*
* @since 7.2
*/
@FunctionalInterface
interface MongoClientSettingsBuilderCustomizer {

/**
* Customize the {@link MongoClientSettings.Builder}.
*
* @param builder the builder to customize, never {@code null}
*/
void customize(MongoClientSettings.Builder builder)
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ class MongoConnectionSourceFactory extends AbstractConnectionSourceFactory<Mongo
@Autowired(required = false)
List<Codec> codecs = []

/**
* Optional customizers applied to the {@link MongoClientSettings.Builder} of the
* default connection source before the {@link MongoClient} is created. This is the
* supported hook for settings that have no {@code grails.mongodb.*} equivalent — e.g.
* registering a driver {@link com.mongodb.event.CommandListener} for metrics/tracing.
*/
@Autowired(required = false)
List<MongoClientSettingsBuilderCustomizer> clientSettingsCustomizers = []

@Override
Serializable getConnectionSourcesConfigurationKey() {
return MongoSettings.SETTING_CONNECTIONS
Expand Down Expand Up @@ -98,6 +107,9 @@ class MongoConnectionSourceFactory extends AbstractConnectionSourceFactory<Mongo
}

builder.applyConnectionString(settings.url)
for (MongoClientSettingsBuilderCustomizer customizer : clientSettingsCustomizers) {
customizer.customize(builder)
}
MongoClient client = MongoClients.create(builder.build())
return new DefaultConnectionSource<MongoClient, MongoConnectionSourceSettings>(name, client, settings)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ package org.grails.datastore.mapping.mongo.config

import com.mongodb.client.MongoClient
import com.mongodb.ReadPreference
import com.mongodb.MongoClientSettings
import com.mongodb.event.CommandListener
import org.grails.datastore.mapping.core.DatastoreUtils
import org.grails.datastore.mapping.core.connections.ConnectionSource
import org.grails.datastore.mapping.core.connections.ConnectionSources
import org.grails.datastore.mapping.core.connections.ConnectionSourcesInitializer
import org.grails.datastore.mapping.mongo.connections.MongoClientSettingsBuilderCustomizer
import org.grails.datastore.mapping.mongo.connections.MongoConnectionSourceFactory
import org.grails.datastore.mapping.mongo.connections.MongoConnectionSourceSettings
import spock.lang.Specification
Expand Down Expand Up @@ -82,4 +85,47 @@ class MongoConnectionSourceFactorySpec extends Specification {
sources?.close()
}

void "test a registered MongoClientSettingsBuilderCustomizer is applied to the default connection source"() {
given: "a customizer that records its invocation and registers a command listener"
CommandListener listener = new CommandListener() {}
MongoClientSettings.Builder captured = null
MongoClientSettingsBuilderCustomizer customizer = { MongoClientSettings.Builder builder ->
captured = builder
builder.addCommandListener(listener)
}

when: "the factory builds connection sources with the customizer registered"
MongoConnectionSourceFactory factory = new MongoConnectionSourceFactory(clientSettingsCustomizers: [customizer])
ConnectionSources<MongoClient, MongoConnectionSourceSettings> sources = ConnectionSourcesInitializer.create(factory, DatastoreUtils.createPropertyResolver(
(MongoSettings.SETTING_URL): "mongodb://localhost/myDb"
))

then: "the customizer was invoked and its command listener applied to the built client settings"
sources.defaultConnectionSource != null
captured != null
captured.build().commandListeners.contains(listener)

cleanup:
sources?.close()
}

void "test multiple MongoClientSettingsBuilderCustomizers are applied in registration order"() {
given: "two customizers that append to a shared list"
List<String> order = []
MongoClientSettingsBuilderCustomizer first = { MongoClientSettings.Builder builder -> order << 'first' }
MongoClientSettingsBuilderCustomizer second = { MongoClientSettings.Builder builder -> order << 'second' }

when: "the factory builds connection sources with both customizers registered"
MongoConnectionSourceFactory factory = new MongoConnectionSourceFactory(clientSettingsCustomizers: [first, second])
ConnectionSources<MongoClient, MongoConnectionSourceSettings> sources = ConnectionSourcesInitializer.create(factory, DatastoreUtils.createPropertyResolver(
(MongoSettings.SETTING_URL): "mongodb://localhost/myDb"
))

then: "both ran, in order"
order == ['first', 'second']

cleanup:
sources?.close()
}

}
Loading